/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#include <array>
#include <boost/process/args.hpp>
#include <boost/process/child.hpp>
#include <boost/regex.hpp>
#include <fstream>
#include <gtest/gtest.h>

#include "ApkResources.h"
#include "Debug.h"
#include "RedexMappedFile.h"
#include "RedexResources.h"
#include "RedexTest.h"
#include "RedexTestUtils.h"
#include "ResourcesTestDefs.h"
#include "Util.h"
#include "androidfw/ResourceTypes.h"
#include "arsc/TestStructures.h"
#include "utils/Errors.h"
#include "utils/Serialize.h"
#include "utils/Visitor.h"

namespace {
// Chunk of just the ResStringPool, as generated by aapt2 (has 2 UTF8 strings)
const std::array<uint8_t, 84> example_data_8{
    {0x01, 0x00, 0x1C, 0x00, 0x54, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
     0x0C, 0x0C, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x2C, 0x20, 0x77, 0x6F, 0x72,
     0x6C, 0x64, 0x00, 0x1C, 0x1C, 0x72, 0x65, 0x73, 0x2F, 0x6C, 0x61, 0x79,
     0x6F, 0x75, 0x74, 0x2F, 0x73, 0x69, 0x6D, 0x70, 0x6C, 0x65, 0x5F, 0x6C,
     0x61, 0x79, 0x6F, 0x75, 0x74, 0x2E, 0x78, 0x6D, 0x6C, 0x00, 0x00, 0x00}};

// Chunk of just the ResStringPool, adapted from example_data_8
// First has only one char which is '\0'
const std::array<uint8_t, 72> example_data_8_2{
    {0x01, 0x00, 0x1C, 0x00, 0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
     0x01, 0x01, 0x00, 0x00, 0x1C, 0x1C, 0x72, 0x65, 0x73, 0x2F, 0x6C, 0x61,
     0x79, 0x6F, 0x75, 0x74, 0x2F, 0x73, 0x69, 0x6D, 0x70, 0x6C, 0x65, 0x5F,
     0x6C, 0x61, 0x79, 0x6F, 0x75, 0x74, 0x2E, 0x78, 0x6D, 0x6C, 0x00, 0x00}};

// Another aapt2 generated ResStringPool, encoded as UTF-16.
const std::array<uint8_t, 116> example_data_16{
    {0x01, 0x00, 0x1C, 0x00, 0x74, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00,
     0x1C, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
     0x05, 0x00, 0x63, 0x00, 0x6F, 0x00, 0x6C, 0x00, 0x6F, 0x00, 0x72, 0x00,
     0x00, 0x00, 0x05, 0x00, 0x64, 0x00, 0x69, 0x00, 0x6D, 0x00, 0x65, 0x00,
     0x6E, 0x00, 0x00, 0x00, 0x02, 0x00, 0x69, 0x00, 0x64, 0x00, 0x00, 0x00,
     0x06, 0x00, 0x6C, 0x00, 0x61, 0x00, 0x79, 0x00, 0x6F, 0x00, 0x75, 0x00,
     0x74, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00,
     0x69, 0x00, 0x6E, 0x00, 0x67, 0x00, 0x00, 0x00}};

// Another ResStringPool encoded as UTF-16. adapted from example_data_16
// First has only one char which is '\0'
const std::array<uint8_t, 108> example_data_16_2{
    {0x01, 0x00, 0x1C, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
     0x14, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00,
     0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x64, 0x00, 0x69, 0x00,
     0x6D, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x02, 0x00, 0x69, 0x00,
     0x64, 0x00, 0x00, 0x00, 0x06, 0x00, 0x6C, 0x00, 0x61, 0x00, 0x79, 0x00,
     0x6F, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00,
     0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x67, 0x00, 0x00, 0x00}};

std::string make_big_string(size_t len) {
  always_assert(len > 4);
  std::string result = "aa" + std::string(len - 4, 'x') + "zz";
  return result;
}

UNUSED void assert_u16_string(const std::u16string& actual_str,
                              const std::string& expected) {
  std::u16string expected_str(expected.begin(), expected.end());
  ASSERT_EQ(actual_str, expected_str);
}

void assert_serialized_data(const void* original,
                            size_t length,
                            android::Vector<char>& serialized) {
  ASSERT_EQ(length, serialized.size());
  for (size_t i = 0; i < length; i++) {
    auto actual = *((const char*)original + i);
    ASSERT_EQ(actual, serialized[i]) << "Mismatch at offset " << i;
  }
}

void write_to_file(const std::string& output_path,
                   android::Vector<char>& data) {
  arsc::write_bytes_to_file(data, output_path);
}

// Runs `aapt d --values resources <an .apk file>` against a zip file containing
// the given .arsc file. If successful, stdout will be returned. This ensures
// that the Android SDK tools can successfully parse our built .arsc files, even
// with all our shenanigans going on :) ;)
std::vector<std::string> aapt_dump_helper(const std::string& arsc_path) {
  std::string arsc_dumper_bin(get_env("arsc_dumper_bin"));
  std::cerr << "Using aapt at: " << get_env("aapt_path") << "\n";

  auto tmp_dir = redex::make_tmp_dir("aapt_dump_helper%%%%%%%%");
  auto out = tmp_dir.path + "/out.txt";
  auto err = tmp_dir.path + "/err.txt";
  boost::process::child c(
      arsc_dumper_bin,
      boost::process::args({"--aapt", get_env("aapt_path"), "--arsc", arsc_path,
                            "--outfile", out, "--errfile", err}));
  c.wait();
  auto exit_code = c.exit_code();
  if (exit_code != 0) {
    std::ifstream fs(err);
    if (fs) {
      std::string line;
      while (std::getline(fs, line)) {
        std::cerr << line << "\n";
      }
    } else {
      std::cerr << "Could not read dump helper errfile" << "\n";
    }
    throw std::runtime_error("aapt dump failed with exit code " +
                             std::to_string(exit_code));
  }
  std::vector<std::string> data;
  std::ifstream fs(out);
  if (fs) {
    std::string line;
    while (std::getline(fs, line)) {
      data.emplace_back(line);
    }
  } else {
    std::cerr << "Could not read dump helper outfile" << "\n";
  }
  return data;
}

struct SimpleEntry {
  uint32_t id;
  std::string name;
  android::Res_value value;
};
struct ComplexValue {
  uint32_t key;
  std::string kind;
  uint32_t data;
};
struct ComplexEntry {
  uint32_t id;
  std::string name;
  uint32_t parent_id;
  std::vector<ComplexValue> values;
};
struct ParsedAaptOutput {
  std::string package_name;
  std::map<uint32_t, uint32_t> flags;
  std::map<uint32_t, std::string> id_fully_qualified_names;
  std::map<std::string, uint32_t> fully_qualified_name_to_id;
  std::map<std::string, std::map<uint32_t, SimpleEntry>>
      config_to_simple_values;
  std::map<std::string, std::map<uint32_t, ComplexEntry>>
      config_to_complex_values;
  // Original output data from the dump command. Dumped to stderr when an assert
  // fails.
  std::vector<std::string> lines;

  void dump() {
    std::cerr << "\n"
              << "Dump command output:" << "\n";
    for (const auto& line : lines) {
      std::cerr << line << "\n";
    }
    std::cerr << "\n"
              << "Dumping parsed package: " << package_name << "\n"
              << "IDs (" << id_fully_qualified_names.size() << "):" << "\n";
    for (auto& pair : id_fully_qualified_names) {
      std::cerr << "  0x" << std::hex << pair.first << std::dec << " ("
                << pair.second << ")" << "\n";
    }
    std::cerr << "Flags (" << flags.size() << "):" << "\n";
    for (auto& pair : flags) {
      std::cerr << "  0x" << std::hex << pair.first << " -> 0x" << pair.second
                << std::dec << "\n";
    }
    std::cerr << "Simple entries:" << "\n";
    for (auto& config_pair : config_to_simple_values) {
      std::cerr << "  config: " << config_pair.first << "\n";
      for (auto& entry_pair : config_pair.second) {
        auto& e = entry_pair.second;
        std::cerr << "    0x" << std::hex << entry_pair.first << " (" << e.name
                  << "): " << "t=0x" << unsigned(e.value.dataType) << " d=0x"
                  << e.value.data << std::dec << "\n";
      }
    }
    std::cerr << "Complex entries:" << "\n";
    for (auto& config_pair : config_to_complex_values) {
      std::cerr << "  config: " << config_pair.first << "\n";
      for (auto& entry_pair : config_pair.second) {
        auto& e = entry_pair.second;
        std::cerr << "    0x" << std::hex << entry_pair.first << " (" << e.name
                  << "): Parent = 0x" << e.parent_id << std::dec << "\n";
        for (auto& v : e.values) {
          std::cerr << "      key = 0x" << std::hex << v.key << " (" << v.kind
                    << "): 0x" << v.data << std::dec << "\n";
        }
      }
    }
  }

  android::Res_value get_simple_value(const std::string& config, uint32_t id) {
    if (config_to_simple_values.count(config) == 0) {
      dump();
      throw std::runtime_error("no simple values for config: " + config);
    }
    auto& map = config_to_simple_values.at(config);
    if (map.count(id) == 0) {
      dump();
      std::stringstream ss;
      ss << "no data for ID 0x" << std::hex << id << " in config: " << config;
      throw std::runtime_error(ss.str());
    }
    auto& entry = map.at(id);
    return entry.value;
  }

  std::vector<ComplexValue> get_complex_values(const std::string& config,
                                               uint32_t id) {
    if (config_to_complex_values.count(config) == 0) {
      dump();
      throw std::runtime_error("no complex values for config: " + config);
    }
    auto& map = config_to_complex_values.at(config);
    if (map.count(id) == 0) {
      dump();
      std::stringstream ss;
      ss << "no data for ID 0x" << std::hex << id << " in config: " << config;
      throw std::runtime_error(ss.str());
    }
    auto& entry = map.at(id);
    return entry.values;
  }

  uint32_t get_identifier(const std::string& fully_qualified) {
    if (fully_qualified_name_to_id.count(fully_qualified) == 0) {
      std::cerr << "No ID found for " << fully_qualified << "\n";
      return 0;
    }
    return fully_qualified_name_to_id.at(fully_qualified);
  }
};

std::string value_to_type_string(const android::Res_value& value) {
  if (value.dataType == android::Res_value::TYPE_NULL) {
    if (value.data == android::Res_value::DATA_NULL_EMPTY) {
      return "null empty";
    } else {
      return "null";
    }
  } else if (value.dataType == android::Res_value::TYPE_REFERENCE) {
    return "reference";
  } else if (value.dataType == android::Res_value::TYPE_DYNAMIC_REFERENCE) {
    return "dynamic reference";
  } else if (value.dataType == android::Res_value::TYPE_ATTRIBUTE) {
    return "attribute";
  } else if (value.dataType == android::Res_value::TYPE_STRING) {
    return "string8"; // this is an assumption but whatever, good enough
  } else if (value.dataType == android::Res_value::TYPE_FLOAT) {
    return "float";
  } else if (value.dataType == android::Res_value::TYPE_DIMENSION) {
    return "dimension";
  } else if (value.dataType == android::Res_value::TYPE_FRACTION) {
    return "fraction";
  } else if (value.dataType >= android::Res_value::TYPE_FIRST_COLOR_INT &&
             value.dataType <= android::Res_value::TYPE_LAST_COLOR_INT) {
    return "color";
  } else if (value.dataType == android::Res_value::TYPE_INT_BOOLEAN) {
    return "boolean";
  } else if (value.dataType >= android::Res_value::TYPE_FIRST_INT &&
             value.dataType <= android::Res_value::TYPE_LAST_INT) {
    return "int";
  } else {
    return "unknown type";
  }
}

// Reads the text output of the form P501204390. aapt d is kind of crappy in
// that it does not print complex values in a nice form (we have to change
// representation to string for types, which is annoying).
// FYI the implementation of the printer is here in case you need to consult it
// or change this in future sdk versions:
// https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/libs/androidfw/ResourceTypes.cpp;l=7544
ParsedAaptOutput aapt_dump_and_parse(const std::string& arsc_path,
                                     bool verbose = false) {
  ParsedAaptOutput output;
  boost::regex package_exp{
      "^Package Group 0 id=0x7f packageCount=1 name=(.*)$"};
  boost::regex spec_exp{
      "^[ ]*spec resource 0x([0-9a-fA-F]+)[^=]+=0x([0-9a-fA-F]+)$"};
  boost::regex config_exp{"^[ ]*config ([^ :]+)[^:]*:$"};
  // Capture the id, package, type/entry, value type, data.
  // Size and r0 are intentionally not captured as we know their hard coded
  // value ahead of time. If they differ, we will not match and fail the test.
  boost::regex simple_exp{
      "^[ ]*resource 0x([0-9a-fA-F]+) ([^:]+):([^:]+): t=0x([0-9a-fA-F]+) "
      "d=0x([0-9a-fA-F]+)..s=0x0008 r=0x00.$"};
  boost::regex bag_exp{
      "^[ ]*resource 0x([0-9a-fA-F]+) ([^:]+):([^:]+): .bag.$"};
  boost::regex bag_parent_exp{"^[ ]*Parent=0x([0-9a-fA-F]+).*$"};
  boost::regex bag_value_exp{
      "^[ ]*#[0-9]+ .Key=0x([0-9a-fA-F]+).: .([a-z0-9 ]+). #([0-9a-fA-F]+)$"};

  auto lines = aapt_dump_helper(arsc_path);
  output.lines = lines;
  std::string current_config;
  enum ComplexState { Unknown, Begin, Values };
  ComplexState state = Unknown;
  // complex_entries[complex_entries.size() - 1] is the entry we're working on
  // that takes multiple lines to complete.
  std::vector<ComplexEntry> complex_entries;
  auto maybe_handle_complex = [&]() {
    if (state == Values) {
      auto& complex_values = output.config_to_complex_values[current_config];
      auto& entry = complex_entries[complex_entries.size() - 1];
      complex_values.emplace(entry.id, entry);
    }
    state = Unknown;
  };
  for (const auto& line : lines) {
    boost::smatch what;
    if (output.package_name.empty() &&
        boost::regex_search(line, what, package_exp)) {
      output.package_name = what[1];
    } else if (boost::regex_search(line, what, spec_exp)) {
      maybe_handle_complex();
      uint32_t id = std::stoul(what[1], nullptr, 16);
      uint32_t flags = std::stoul(what[2], nullptr, 16);
      output.flags.emplace(id, flags);
    } else if (boost::regex_search(line, what, config_exp)) {
      maybe_handle_complex();
      auto name = what[1].str();
      if (strcmp(name.c_str(), "(default)") == 0) {
        current_config = "default";
      } else {
        current_config = name;
      }
    } else if (boost::regex_search(line, what, simple_exp)) {
      maybe_handle_complex();
      auto& simple_values = output.config_to_simple_values[current_config];
      uint32_t id = std::stoul(what[1], nullptr, 16);
      auto fully_qualified = what[2].str() + ":" + what[3].str();
      uint32_t type = std::stoul(what[4], nullptr, 16);
      if (type > android::Res_value::TYPE_LAST_INT) {
        std::cerr << "Bad type: " << line << "\n";
        continue;
      }
      uint32_t data = std::stoul(what[5], nullptr, 16);
      output.id_fully_qualified_names.emplace(id, fully_qualified);
      output.fully_qualified_name_to_id.emplace(fully_qualified, id);
      android::Res_value value{sizeof(android::Res_value), 0, (uint8_t)type,
                               data};
      SimpleEntry entry{id, fully_qualified, value};
      simple_values.emplace(id, std::move(entry));
    } else if (boost::regex_search(line, what, bag_exp)) {
      maybe_handle_complex();
      state = Begin;
      ComplexEntry entry{};
      entry.id = std::stoul(what[1], nullptr, 16);
      entry.name = what[2].str() + ":" + what[3].str();
      output.id_fully_qualified_names.emplace(entry.id, entry.name);
      output.fully_qualified_name_to_id.emplace(entry.name, entry.id);
      complex_entries.emplace_back(std::move(entry));
    } else if (boost::regex_search(line, what, bag_parent_exp)) {
      always_assert_log(state == Begin, "Unexpected state at line: %s",
                        line.c_str());
      auto& entry = complex_entries[complex_entries.size() - 1];
      entry.parent_id = std::stoul(what[1], nullptr, 16);
      state = Values;
    } else if (boost::regex_search(line, what, bag_value_exp)) {
      always_assert_log(state == Values, "Unexpected state at line: %s",
                        line.c_str());
      ComplexValue value{(uint32_t)std::stoul(what[1], nullptr, 16), what[2],
                         (uint32_t)std::stoul(what[3], nullptr, 16)};
      auto& entry = complex_entries[complex_entries.size() - 1];
      entry.values.emplace_back(std::move(value));
    }
  }
  maybe_handle_complex();

  // End of parsing, optionally spew our representation to stderr.
  if (verbose) {
    output.dump();
  }
  return output;
}
} // namespace

TEST(ResStringPool, AppendStringInXmlLayout) {
  std::string to_add("test_test");
  auto f = RedexMappedFile::open(get_env("test_layout_path"));

  android::Vector<char> serialized;
  size_t new_idx;
  EXPECT_EQ(arsc::ensure_string_in_xml_pool(f.const_data(), f.size(), to_add,
                                            &serialized, &new_idx),
            android::OK);
  EXPECT_EQ(new_idx, 19);
  EXPECT_FALSE(serialized.empty());
  auto* serialized_data = (char*)serialized.array();
  const auto chunk_size = sizeof(android::ResChunk_header);
  auto* pool_ptr =
      (android::ResStringPool_header*)(serialized_data + chunk_size);
  android::ResStringPool pool(pool_ptr, dtohl(pool_ptr->header.size));
  auto new_str = arsc::get_string_from_pool(pool, new_idx);
  EXPECT_EQ(new_str, "test_test");

  // Append the data again, we should get a successful result with the same
  // index and no outputted binary data.
  android::Vector<char> again;
  size_t again_idx;
  EXPECT_EQ(arsc::ensure_string_in_xml_pool(serialized_data, serialized.size(),
                                            to_add, &again, &again_idx),
            android::OK);
  EXPECT_EQ(again_idx, new_idx);
  EXPECT_TRUE(again.empty());
}

TEST(ResStringPool, AppendStringsInXmlLayout) {
  // The test will request 3 strings to be added, one of which will already
  // exist in the file's pool. We should get back sensible data with the two new
  // ones added at the back of pool.
  std::set<std::string> strings_to_add{"TextView", "aaaaa", "bbbbb"};
  std::unordered_map<std::string, uint32_t> string_to_idx;
  auto f = RedexMappedFile::open(get_env("test_layout_path"));

  android::Vector<char> serialized;
  EXPECT_EQ(arsc::ensure_strings_in_xml_pool(f.const_data(), f.size(),
                                             strings_to_add, &serialized,
                                             &string_to_idx),
            android::OK);

  EXPECT_EQ(string_to_idx.at("TextView"), 11);
  EXPECT_EQ(string_to_idx.at("aaaaa"), 19);
  EXPECT_EQ(string_to_idx.at("bbbbb"), 20);
  EXPECT_FALSE(serialized.empty());
  auto* serialized_data = (char*)serialized.array();
  auto* pool_ptr =
      (android::ResStringPool_header*)(serialized_data +
                                       sizeof(android::ResChunk_header));
  android::ResStringPool pool(pool_ptr, dtohl(pool_ptr->header.size));
  EXPECT_EQ(arsc::get_string_from_pool(pool, 11), "TextView");
  EXPECT_EQ(arsc::get_string_from_pool(pool, 19), "aaaaa");
  EXPECT_EQ(arsc::get_string_from_pool(pool, 20), "bbbbb");

  // Append the data again, we should get a successful result with the same
  // index and no outputted binary data.
  android::Vector<char> again;
  std::unordered_map<std::string, uint32_t> again_idx;
  EXPECT_EQ(arsc::ensure_strings_in_xml_pool(serialized_data, serialized.size(),
                                             strings_to_add, &again,
                                             &again_idx),
            android::OK);
  EXPECT_EQ(again_idx.size(), 3);
  EXPECT_EQ(again_idx.at("TextView"), 11);
  EXPECT_EQ(again_idx.at("aaaaa"), 19);
  EXPECT_EQ(again_idx.at("bbbbb"), 20);
  EXPECT_TRUE(again.empty());
}

TEST(ResStringPool, ReplaceStringsInXmlLayout) {
  // Given layout file should have a series of View subclasses in the XML, which
  // we will rename. Parse the resulting binary data, and make sure all tags are
  // right.
  auto f = RedexMappedFile::open(get_env("test_layout_path"));

  std::map<std::string, std::string> shortened_names;
  shortened_names.emplace("com.example.test.CustomViewGroup", "Z.a");
  shortened_names.emplace("com.example.test.CustomTextView", "Z.b");
  shortened_names.emplace("com.example.test.CustomButton", "Z.c");
  shortened_names.emplace("com.example.test.NotFound", "Z.d");

  android::Vector<char> serialized;
  size_t num_renamed = 0;
  ApkResources resources("");
  resources.replace_in_xml_string_pool(
      f.const_data(), f.size(), shortened_names, &serialized, &num_renamed);

  EXPECT_EQ(num_renamed, 3);
  android::ResXMLTree parser;
  parser.setTo(serialized.array(), serialized.size());
  EXPECT_EQ(android::NO_ERROR, parser.getError())
      << "Error parsing layout after rename";

  std::vector<std::string> expected_xml_tags;
  expected_xml_tags.emplace_back("Z.a");
  expected_xml_tags.emplace_back("TextView");
  expected_xml_tags.emplace_back("Z.b");
  expected_xml_tags.emplace_back("Z.c");
  expected_xml_tags.emplace_back("Button");

  size_t tag_count = 0;
  android::ResXMLParser::event_code_t type;
  do {
    type = parser.next();
    if (type == android::ResXMLParser::START_TAG) {
      EXPECT_LT(tag_count, 5);
      size_t len;
      android::String8 tag(parser.getElementName(&len));
      const auto* actual_chars = tag.string();
      const auto* expected_chars = expected_xml_tags[tag_count].c_str();
      EXPECT_STREQ(actual_chars, expected_chars);
      tag_count++;
    }
  } while (type != android::ResXMLParser::BAD_DOCUMENT &&
           type != android::ResXMLParser::END_DOCUMENT);
  EXPECT_EQ(tag_count, 5);
}

TEST(ResTable, AppendNewType) {
  auto src_file_path = get_env("test_arsc_path");
  auto tmp_dir = redex::make_tmp_dir("ResTable_AppendNewType%%%%%%%%");
  auto dest_file_path = tmp_dir.path + "/resources.arsc";
  redex::copy_file(src_file_path, dest_file_path);

  auto src = RedexMappedFile::open(src_file_path);
  // Set up existing entry data to copy into a different type
  const uint8_t dest_type = 3;
  std::vector<uint32_t> source_ids;
  source_ids.push_back(0x7f010000);
  size_t num_ids = source_ids.size();
  std::vector<android::Res_value> values;
  // Read the number of original types.
  std::vector<std::string> original_type_names;
  {
    apk::TableSnapshot table_snapshot(src, src.size());
    table_snapshot.get_type_names(APPLICATION_PACKAGE, &original_type_names);
    for (size_t i = 0; i < num_ids; i++) {
      table_snapshot.collect_resource_values(source_ids[i], &values);
    }
  }

  // Write a new .arsc file
  {
    ResourcesArscFile arsc_file(dest_file_path);
    std::vector<android::ResTable_config*> config_ptrs;
    config_ptrs.emplace_back(&default_config);
    arsc_file.define_type(0x7f, 3, "dimen.2", config_ptrs, source_ids);
    arsc_file.serialize();
  }

  auto round_trip_dump = aapt_dump_and_parse(dest_file_path);
  auto dest = RedexMappedFile::open(dest_file_path);
  apk::TableSnapshot round_trip_snapshot(dest, dest.size());
  // Make sure entries exist in 0x7f03xxxx range
  for (size_t i = 0; i < num_ids; i++) {
    auto old_id = source_ids[i];
    auto new_id = 0x7f000000 | (dest_type << 16) | (old_id & 0xFFFF);
    android::Res_value expected = values[i];
    auto actual = round_trip_dump.get_simple_value("default", new_id);
    ASSERT_EQ(expected.dataType, actual.dataType);
    ASSERT_EQ(expected.data, actual.data);
  }

  // Sanity check values in their original location
  {
    auto out_value = round_trip_dump.get_simple_value("default", 0x7f010000);
    float val = arsc::complex_value(out_value.data);
    uint32_t unit = arsc::complex_unit(out_value.data, false);
    ASSERT_EQ((int)val, 10);
    ASSERT_EQ(unit, android::Res_value::COMPLEX_UNIT_DIP);
  }
  {
    auto out_value = round_trip_dump.get_simple_value("default", 0x7f010001);
    float val = arsc::complex_value(out_value.data);
    uint32_t unit = arsc::complex_unit(out_value.data, false);
    ASSERT_EQ((int)val, 20);
    ASSERT_EQ(unit, android::Res_value::COMPLEX_UNIT_DIP);
  }

  std::vector<std::string> type_names;
  round_trip_snapshot.get_type_names(APPLICATION_PACKAGE, &type_names);
  ASSERT_EQ(type_names.size(), original_type_names.size() + 1);

  {
    auto type_ids_to_names =
        round_trip_snapshot.get_type_ids_to_resource_type_names(
            APPLICATION_PACKAGE);
    ASSERT_EQ(type_ids_to_names.size(), 3);
    ASSERT_STREQ(type_ids_to_names[1].c_str(), "dimen");
    ASSERT_STREQ(type_ids_to_names[2].c_str(), "string");
    ASSERT_STREQ(type_ids_to_names[3].c_str(), "dimen.2");
  }
  {
    auto type_ids_to_names =
        round_trip_snapshot.get_type_ids_to_resource_type_names(
            APPLICATION_PACKAGE, true);
    ASSERT_EQ(type_ids_to_names.size(), 3);
    ASSERT_STREQ(type_ids_to_names[1].c_str(), "dimen");
    ASSERT_STREQ(type_ids_to_names[2].c_str(), "string");
    ASSERT_STREQ(type_ids_to_names[3].c_str(), "dimen");
  }
}

TEST(ResStringPoolBuilder, TestPoolRebuild8) {
  android::ResStringPool pool(&example_data_8, example_data_8.size(), false);
  auto flags =
      (pool.isUTF8() ? android::ResStringPool_header::UTF8_FLAG : 0) |
      (pool.isSorted() ? android::ResStringPool_header::SORTED_FLAG : 0);
  auto pool_size = pool.size();

  arsc::ResStringPoolBuilder builder(flags);
  for (size_t i = 0; i < pool_size; i++) {
    size_t out_len;
    const auto* s = pool.string8At(i, &out_len);
    builder.add_string(s, out_len);
  }
  android::Vector<char> serialized;
  builder.serialize(&serialized);
  assert_serialized_data(&example_data_8, example_data_8.size(), serialized);
}

TEST(ResStringPoolBuilder, TestPoolRebuild16) {
  android::ResStringPool pool(&example_data_16, example_data_16.size(), false);
  auto flags =
      (pool.isUTF8() ? android::ResStringPool_header::UTF8_FLAG : 0) |
      (pool.isSorted() ? android::ResStringPool_header::SORTED_FLAG : 0);
  auto pool_size = pool.size();

  arsc::ResStringPoolBuilder builder(flags);
  for (size_t i = 0; i < pool_size; i++) {
    size_t out_len;
    const auto* s = pool.stringAt(i, &out_len);
    builder.add_string(s, out_len);
  }
  android::Vector<char> serialized;
  builder.serialize(&serialized);
  assert_serialized_data(&example_data_16, example_data_16.size(), serialized);
}

TEST(ResStringPoolBuilder, TestPoolRebuild16OneChar) {
  android::ResStringPool pool(&example_data_16_2, example_data_16_2.size(),
                              false);
  auto flags =
      (pool.isUTF8() ? android::ResStringPool_header::UTF8_FLAG : 0) |
      (pool.isSorted() ? android::ResStringPool_header::SORTED_FLAG : 0);
  auto pool_size = pool.size();

  arsc::ResStringPoolBuilder builder(flags);
  for (size_t i = 0; i < pool_size; i++) {
    size_t out_len;
    const auto* s = pool.stringAt(i, &out_len);
    builder.add_string(s, out_len);
  }
  android::Vector<char> serialized;
  builder.serialize(&serialized);
  assert_serialized_data(&example_data_16_2, example_data_16_2.size(),
                         serialized);
}

TEST(ResStringPoolBuilder, TestPoolRebuildStyle8) {
  const std::array<uint8_t, 232> data{
      {0x01, 0x00, 0x1c, 0x00, 0xe8, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
       0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
       0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
       0x41, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00,
       0x59, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       0x1c, 0x00, 0x00, 0x00, 0x11, 0x11, 0x41, 0x6e, 0x20, 0x75, 0x6e, 0x75,
       0x73, 0x65, 0x64, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x00,
       0x2a, 0x2a, 0x49, 0x20, 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x61, 0x20, 0x66,
       0x69, 0x6e, 0x65, 0x20, 0x67, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x6f, 0x66,
       0x20, 0x48, 0x32, 0x4f, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20,
       0x6d, 0x6f, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x21, 0x00, 0x0c, 0x0c, 0x48,
       0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x00,
       0x01, 0x01, 0x62, 0x00, 0x02, 0x02, 0x65, 0x6d, 0x00, 0x03, 0x03, 0x73,
       0x75, 0x62, 0x00, 0x03, 0x03, 0x73, 0x75, 0x70, 0x00, 0x00, 0x00, 0x00,
       0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
       0x06, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
       0xff, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
       0x0c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
       0x18, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
       0xff, 0xff, 0xff, 0xff}};
  android::ResStringPool pool(&data, data.size(), false);
  auto flags =
      (pool.isUTF8() ? android::ResStringPool_header::UTF8_FLAG : 0) |
      (pool.isSorted() ? android::ResStringPool_header::SORTED_FLAG : 0);
  auto pool_size = pool.size();
  auto style_count = pool.styleCount();
  arsc::ResStringPoolBuilder builder(flags);
  for (size_t i = 0; i < pool_size; i++) {
    size_t out_len;
    const auto* s = pool.string8At(i, &out_len);
    if (i < style_count) {
      arsc::SpanVector vec;
      auto* span = (android::ResStringPool_span*)pool.styleAt(i);
      arsc::collect_spans(span, &vec);
      builder.add_style(s, out_len, vec);
    } else {
      builder.add_string(s, out_len);
    }
  }
  EXPECT_EQ(pool.size(), builder.string_count());
  android::Vector<char> serialized;
  builder.serialize(&serialized);
  assert_serialized_data(&data, data.size(), serialized);
}

TEST(ResStringPoolBuilder, TestPoolRebuildStyle8EmptyChar) {
  android::ResStringPool pool(&example_data_8_2, example_data_8_2.size(),
                              false);
  auto flags =
      (pool.isUTF8() ? android::ResStringPool_header::UTF8_FLAG : 0) |
      (pool.isSorted() ? android::ResStringPool_header::SORTED_FLAG : 0);
  auto pool_size = pool.size();
  auto style_count = pool.styleCount();
  arsc::ResStringPoolBuilder builder(flags);
  for (size_t i = 0; i < pool_size; i++) {
    size_t out_len;
    const auto* s = pool.string8At(i, &out_len);
    if (i < style_count) {
      arsc::SpanVector vec;
      auto* span = (android::ResStringPool_span*)pool.styleAt(i);
      arsc::collect_spans(span, &vec);
      builder.add_style(s, out_len, vec);
    } else {
      builder.add_string(s, out_len);
    }
  }
  EXPECT_EQ(pool.size(), builder.string_count());
  android::Vector<char> serialized;
  builder.serialize(&serialized);
  assert_serialized_data(&example_data_8_2, example_data_8_2.size(),
                         serialized);
}

TEST(ResStringPoolBuilder, TestAllTheOptions) {
  std::string hello_world("Hello world!");
  android::String16 hello_world16("Hello world!");
  android::String8 foo("foo");
  android::String16 bar("bar");
  auto big_std_string = make_big_string(1000);
  std::string em("em");

  android::ResStringPool_span span{
      .name = {.index = 4}, .firstChar = 6, .lastChar = 10};
  std::vector<android::ResStringPool_span*> spans = {&span};

  auto do_validation = [&](void* data, size_t size) {
    android::ResStringPool pool;
    EXPECT_EQ(pool.setTo(data, size, true), 0);
    EXPECT_EQ(pool.styleCount(), 1);
    EXPECT_EQ(pool.size(), 5);
    EXPECT_STREQ(arsc::get_string_from_pool(pool, 0).c_str(), "Hello world!");
    EXPECT_STREQ(arsc::get_string_from_pool(pool, 1).c_str(), "foo");
    EXPECT_STREQ(arsc::get_string_from_pool(pool, 2).c_str(), "bar");
    EXPECT_STREQ(arsc::get_string_from_pool(pool, 3).c_str(),
                 big_std_string.c_str());
    EXPECT_STREQ(arsc::get_string_from_pool(pool, 4).c_str(), "em");
  };

  {
    arsc::ResStringPoolBuilder builder(0);
    builder.add_style(hello_world, spans);
    builder.add_string(foo.string(), foo.size());
    builder.add_string(bar.string(), bar.size());
    builder.add_string(big_std_string);
    builder.add_string(em);

    android::Vector<char> out;
    builder.serialize(&out);
    do_validation((void*)out.array(), out.size());
  }
  {
    arsc::ResStringPoolBuilder builder(
        android::ResStringPool_header::UTF8_FLAG);
    builder.add_style(hello_world16.string(), hello_world16.size(), spans);
    builder.add_string(foo.string(), foo.size());
    builder.add_string(bar.string(), bar.size());
    builder.add_string(big_std_string);
    builder.add_string(em);

    android::Vector<char> out;
    builder.serialize(&out);
    do_validation((void*)out.array(), out.size());
  }
}

TEST(ResTableParse, TestUnknownPackageChunks) {
  // A table with one package, which has a fake chunk that is made up. The chunk
  // that is not known/recognized should just be copied as-is to the output.
  auto tmp_dir = redex::make_tmp_dir("ResTableParse%%%%%%%%");
  auto res_path = tmp_dir.path + "/resources.arsc";
  redex::copy_file(get_env("resources_unknown_chunk"), res_path);
  ResourcesArscFile res_table(res_path);
  ResourceConfig config;
  // Be explicit here for when the default value of this config option gets
  // changed. The intent of this test is to verify our parsers/builders can be a
  // simple round trip with no changes.
  config.sort_key_strings = false;
  res_table.finalize_resource_table(config);
  EXPECT_TRUE(
      redex::are_files_equal(get_env("resources_unknown_chunk"), res_path));
}

TEST(Configs, TestConfigEquivalence) {
  EXPECT_TRUE(arsc::are_configs_equivalent(&default_config, &default_config));
  EXPECT_FALSE(arsc::are_configs_equivalent(&default_config, &land_config));
  EXPECT_TRUE(arsc::is_default_config(&default_config));
  // Configs of different sizes (simulate some of our snapshots of older files)
  {
    struct SmallConfig {
      uint32_t a;
      uint32_t b;
      uint32_t c;
      uint32_t d;
    };
    SmallConfig small_config{};
    small_config.a = sizeof(SmallConfig);
    EXPECT_TRUE(arsc::are_configs_equivalent(
        &default_config, (android::ResTable_config*)&small_config));
  }
  {
    PACKED(struct BiggerConfig {
      android::ResTable_config config{};
      uint32_t a;
      uint32_t b;
      uint32_t c;
      uint32_t d;
    });
    BiggerConfig big_config{};
    big_config.config = default_config;
    big_config.config.size = sizeof(BiggerConfig);
    // Can't determine default values of newer versions we don't know about.
    EXPECT_FALSE(arsc::are_configs_equivalent(
        &default_config, (android::ResTable_config*)&big_config));
  }
}

namespace {
class TypeSpecCollector : public arsc::ResourceTableVisitor {
 public:
  bool visit_type_spec(android::ResTable_package* package,
                       android::ResTable_typeSpec* type_spec) override {
    type_specs.emplace(type_spec->id, type_spec);
    return arsc::ResourceTableVisitor::visit_type_spec(package, type_spec);
  }

  // Sets the reserved short to 0, like older tool versions would do.
  void clear_reserved_fields() {
    for (auto&& [id, ptr] : type_specs) {
      ptr->res1 = 0;
    }
  }

  // Get the repurposed short field in the header.
  uint16_t get_reserved_field(uint8_t type_id) {
    return type_specs.at(type_id)->res1;
  }

  std::map<uint8_t, android::ResTable_typeSpec*> type_specs;
};

void build_arsc_file_and_validate(
    const std::function<void(const std::string& temp_dir,
                             const std::string& arsc_path)>& callback) {
  auto pool_flags = android::ResStringPool_header::UTF8_FLAG;
  auto global_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);

  auto key_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  constexpr uint16_t ENTRY_COUNT = 4;
  const char* entry_names[ENTRY_COUNT] = {"first", "second", "third", "fourth"};
  for (uint16_t i = 0; i < ENTRY_COUNT; i++) {
    key_strings_builder->add_string(entry_names[i], strlen(entry_names[i]));
  }

  auto type_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  constexpr uint8_t TYPE_COUNT = 2;
  const char* type_names[2] = {"dimen", "style"};
  for (uint16_t i = 0; i < TYPE_COUNT; i++) {
    type_strings_builder->add_string(type_names[i], strlen(type_names[i]));
  }

  auto package_builder =
      std::make_shared<arsc::ResPackageBuilder>(&foo_package);
  package_builder->set_key_strings(key_strings_builder);
  package_builder->set_type_strings(type_strings_builder);

  auto table_builder = std::make_shared<arsc::ResTableBuilder>();
  table_builder->set_global_strings(global_strings_builder);
  table_builder->add_package(package_builder);

  // dimen
  std::vector<android::ResTable_config*> dimen_configs = {&default_config,
                                                          &land_config};
  // First res ID has entries in two different configs (this flag denotes that).
  // Subsequent two entries only have default config entries (hence zero).
  std::vector<uint32_t> dimen_flags = {
      android::ResTable_config::CONFIG_ORIENTATION, 0, 0};
  auto dimen_type_definer = std::make_shared<arsc::ResTableTypeDefiner>(
      foo_package.id, 1, dimen_configs, dimen_flags);
  package_builder->add_type(dimen_type_definer);

  dimen_type_definer->add(&default_config, &e0);
  dimen_type_definer->add(&land_config, &e0_land);
  dimen_type_definer->add(&default_config, &e1);
  dimen_type_definer->add_empty(&land_config);
  dimen_type_definer->add(&default_config, &e2);
  dimen_type_definer->add_empty(&land_config);

  // style
  std::vector<android::ResTable_config*> style_configs = {&default_config,
                                                          &xxhdpi_config};
  std::vector<uint32_t> style_flags = {
      android::ResTable_config::CONFIG_DENSITY};
  auto style_type_definer = std::make_shared<arsc::ResTableTypeDefiner>(
      foo_package.id, 2, style_configs, style_flags);
  package_builder->add_type(style_type_definer);

  arsc::ResComplexEntryBuilder complex_entry_builder;
  complex_entry_builder.set_key_string_index(3);
  complex_entry_builder.set_parent_id(0);

  std::vector<std::tuple<uint8_t, uint32_t, uint32_t>> attributes = {
      {android::Res_value::TYPE_INT_COLOR_RGB8, 0xFF0000FF, 0x01010098},
      {android::Res_value::TYPE_INT_COLOR_RGB8, 0xFF00FF00, 0x010100d4}};

  for (const auto& [data_type, data_value, attr_id] : attributes) {
    android::Res_value value;
    value.size = sizeof(android::Res_value);
    value.dataType = data_type;
    value.data = data_value;
    complex_entry_builder.add(attr_id, value);
  }

  android::Vector<char> complex_entry_data;
  complex_entry_builder.serialize(&complex_entry_data);

  style_type_definer->add(
      &default_config,
      {(uint8_t*)complex_entry_data.array(), complex_entry_data.size()});
  style_type_definer->add(&xxhdpi_config, {(uint8_t*)complex_entry_data.array(),
                                           complex_entry_data.size()});

  // Write to a file, give the callback the temp dir and file to validate
  // against.
  android::Vector<char> out;
  table_builder->serialize(&out);

  auto tmp_dir = redex::make_tmp_dir("ResTable_BuildNewTable%%%%%%%%");
  auto dest_file_path = tmp_dir.path + "/resources.arsc";
  std::cerr << "Writing new table to " << dest_file_path.c_str() << "\n";
  write_to_file(dest_file_path, out);
  callback(tmp_dir.path, dest_file_path);
}

void delete_resources(const std::string& arsc_file_path,
                      const std::vector<uint32_t>& ids_to_delete) {
  ResourcesArscFile arsc_file(arsc_file_path);
  // Should delete all of the style entries and omit that typeSpec and
  // type entirely during serialization.
  for (const auto& id : ids_to_delete) {
    arsc_file.delete_resource(id);
  }
  arsc_file.serialize();
  // We actually have to reload the table, since all modifications are
  // meant to be written directly to disk afterwards.
  ResourcesArscFile reloaded_file(arsc_file_path);
  reloaded_file.finalize_resource_table({});
}

UNUSED int32_t load_global_strings(const RedexMappedFile& arsc_file,
                                   android::ResStringPool* pool) {
  apk::TableParser parser;
  parser.visit((void*)arsc_file.const_data(), arsc_file.size());
  auto* pool_header = parser.m_global_pool_header;
  return pool->setTo(pool_header, pool_header->header.size, true);
}

int32_t load_key_strings(const RedexMappedFile& arsc_file,
                         android::ResStringPool* pool) {
  apk::TableParser parser;
  parser.visit((void*)arsc_file.const_data(), arsc_file.size());
  // Only 1 package in our test arsc file.
  auto* pool_header = parser.m_package_key_string_headers.begin()->second;
  return pool->setTo(pool_header, pool_header->header.size, true);
}

int32_t load_type_strings(const RedexMappedFile& arsc_file,
                          android::ResStringPool* pool) {
  apk::TableParser parser;
  parser.visit((void*)arsc_file.const_data(), arsc_file.size());
  // Only 1 package in our test arsc file.
  auto* pool_header = parser.m_package_type_string_headers.begin()->second;
  return pool->setTo(pool_header, pool_header->header.size, true);
}

std::vector<arsc::TypeInfo> load_types(const RedexMappedFile& arsc_file) {
  apk::TableParser parser;
  parser.visit((void*)arsc_file.const_data(), arsc_file.size());
  return parser.m_package_types.begin()->second;
}

// Assert values in the table match the expected "EntryAndValue"
#define ASSERT_ENTRY_VALUES(table, config_str, entry_str, expected) \
  ({                                                                \
    uint32_t __id = (table).get_identifier(entry_str);              \
    android::Res_value __actual_value =                             \
        (table).get_simple_value((config_str), __id);               \
    EXPECT_EQ((expected).value.size, __actual_value.size);          \
    EXPECT_EQ((expected).value.dataType, __actual_value.dataType);  \
    EXPECT_EQ((expected).value.data, __actual_value.data);          \
  })
// Assert values in the table match the two items expecrted in the
// "MapEntryAndTwoValues"
#define ASSERT_MAP_ENTRY_VALUES(table, config_str, entry_str, expected) \
  ({                                                                    \
    uint32_t __id = (table).get_identifier(entry_str);                  \
    std::vector __vec = (table).get_complex_values((config_str), __id); \
    EXPECT_EQ(__vec.size(), 2);                                         \
    auto& __vec0 = __vec[0];                                            \
    auto& __vec1 = __vec[1];                                            \
    EXPECT_EQ(__vec0.key, (expected).item0.name.ident);                 \
    EXPECT_STREQ(__vec0.kind.c_str(),                                   \
                 value_to_type_string((expected).item0.value).c_str()); \
    EXPECT_EQ(__vec0.data, (expected).item0.value.data);                \
    EXPECT_EQ(__vec1.key, (expected).item1.name.ident);                 \
    EXPECT_STREQ(__vec1.kind.c_str(),                                   \
                 value_to_type_string((expected).item1.value).c_str()); \
    EXPECT_EQ(__vec1.data, (expected).item1.value.data);                \
  })
// As above, but look up via resource ID instead of string
#define ASSERT_ID_VALUES(table, config_str, id, expected)          \
  ({                                                               \
    android::Res_value __actual_value =                            \
        (table).get_simple_value((config_str), (id));              \
    EXPECT_EQ((expected).value.size, __actual_value.size);         \
    EXPECT_EQ((expected).value.dataType, __actual_value.dataType); \
    EXPECT_EQ((expected).value.data, __actual_value.data);         \
  })
} // namespace

TEST(ResTable, TestBuilderRoundTrip) {
  auto tmp_dir = redex::make_tmp_dir("ResTable%%%%%%%%");
  auto res_path = tmp_dir.path + "/resources.arsc";
  redex::copy_file(get_env("test_arsc_path"), res_path);
  ResourcesArscFile res_table(res_path);
  res_table.serialize();
  // To match the old, checked in binary that was made with outdated aapt tool,
  // stamp over the reserved fields that got repurposed to set them to be zero.
  {
    auto f = RedexMappedFile::open(res_path, false);
    TypeSpecCollector collector;
    collector.visit(f.data(), f.size());
    collector.clear_reserved_fields();
  }
  EXPECT_TRUE(redex::are_files_equal(get_env("test_arsc_path"), res_path));
}

TEST(ResTable, BuildNewTable) {
  build_arsc_file_and_validate([&](const std::string& /* unused */,
                                   const std::string& arsc_path) {
    // Now, use unforked AOSP APIs to read out the data to make sure it
    // matches the stuff we put in.
    auto table_dump = aapt_dump_and_parse(arsc_path);
    // 0x7f010000
    ASSERT_ENTRY_VALUES(table_dump, "default", "foo:dimen/first", e0);
    // 0x7f010001
    ASSERT_ENTRY_VALUES(table_dump, "default", "foo:dimen/second", e1);
    // 0x7f010002
    ASSERT_ENTRY_VALUES(table_dump, "default", "foo:dimen/third", e2);
    // Rotate to landscape should get different values for entry 0x7f010000
    ASSERT_ENTRY_VALUES(table_dump, "land", "foo:dimen/first", e0_land);
    // Separate validation for plurals, styles, etc.
    ASSERT_MAP_ENTRY_VALUES(table_dump, "xxhdpi", "foo:style/fourth", style);
    // Sanity check header values for number of types in each type spec.
    auto f = RedexMappedFile::open(arsc_path, false);
    TypeSpecCollector collector;
    collector.visit(f.data(), f.size());
    ASSERT_EQ(collector.get_reserved_field(0x1), 2)
        << "dimen should have two ResTable_type entries";
    ASSERT_EQ(collector.get_reserved_field(0x2), 2)
        << "style should have two ResTable_type entries";
  });
}

TEST(ResTable, ComputeSizes) {
  EntryAndValue simple(0, android::Res_value::TYPE_DIMENSION, 1000);
  EXPECT_EQ(arsc::compute_entry_value_length(&simple.entry),
            sizeof(EntryAndValue));

  arsc::ResComplexEntryBuilder complex_builder;
  complex_builder.set_key_string_index(1);
  complex_builder.add(0x01010098, android::Res_value::TYPE_INT_COLOR_RGB8,
                      0xff0000ff);

  android::Vector<char> complex_entry_data;
  complex_builder.serialize(&complex_entry_data);

  android::ResTable_entry* complex_entry_ptr =
      (android::ResTable_entry*)complex_entry_data.array();

  size_t expected_size =
      sizeof(android::ResTable_map_entry) + sizeof(android::ResTable_map);
  EXPECT_EQ(arsc::compute_entry_value_length(complex_entry_ptr), expected_size);
}

TEST(ResTable, DeleteAllEntriesInType) {
  build_arsc_file_and_validate(
      [&](const std::string& /* unused */, const std::string& arsc_path) {
        // Delete everything in the style type.
        delete_resources(arsc_path, {0x7f020000});
        // Make sure the table is still valid after deletion.
        auto table_dump = aapt_dump_and_parse(arsc_path);
        // 0x7f010000
        ASSERT_ENTRY_VALUES(table_dump, "default", "foo:dimen/first", e0);
        // 0x7f010001
        ASSERT_ENTRY_VALUES(table_dump, "default", "foo:dimen/third", e2);
        // Rotate to landscape should get different values for entry 0x7f010000
        ASSERT_ENTRY_VALUES(table_dump, "land", "foo:dimen/first", e0_land);

        // This one should have been deleted!!
        EXPECT_EQ(table_dump.get_identifier("foo:style/fourth"), 0)
            << "Style was not properly deleted!";

        // Check the validity of the string pools and ensure data is getting
        // fully cleaned up.
        auto built_arsc_file = RedexMappedFile::open(arsc_path);
        {
          // Make sure the key for "fourth" got deleted.
          android::ResStringPool pool;
          EXPECT_EQ(load_key_strings(built_arsc_file, &pool), 0);
          EXPECT_EQ(pool.size(), 3);
          std::unordered_set<std::string> string_values;
          for (size_t i = 0; i < pool.size(); i++) {
            string_values.emplace(arsc::get_string_from_pool(pool, i));
          }
          EXPECT_EQ(string_values.count("fourth"), 0);
          EXPECT_EQ(string_values.count("first"), 1);
          EXPECT_EQ(string_values.count("second"), 1);
          EXPECT_EQ(string_values.count("third"), 1);
        }
        {
          android::ResStringPool pool;
          EXPECT_EQ(load_type_strings(built_arsc_file, &pool), 0);
          std::unordered_set<std::string> string_values;
          for (size_t i = 0; i < pool.size(); i++) {
            string_values.emplace(arsc::get_string_from_pool(pool, i));
          }
          EXPECT_EQ(string_values.count("dimen"), 1);
          EXPECT_EQ(string_values.count("style"), 0);
        }

        // Ensure that we have only 1 ResTable_typeSpec, but two configs within.
        auto type_infos = load_types(built_arsc_file);
        EXPECT_EQ(type_infos.size(), 1) << "ResTable_typeSpec not cleaned up!";
        EXPECT_EQ(type_infos.at(0).configs.size(), 2);
      });
}

TEST(ResTable, DeleteAllLandscapeEntries) {
  build_arsc_file_and_validate([&](const std::string& /* unused */,
                                   const std::string& arsc_path) {
    // Delete the lone dimen entry that has a landscape override.
    delete_resources(arsc_path, {0x7f010000});
    // Make sure the table is still valid after deletion.
    auto table_dump = aapt_dump_and_parse(arsc_path);
    // This one should have been deleted!!
    EXPECT_EQ(table_dump.get_identifier("foo:dimen/first"), 0)
        << "Entry was not properly deleted!";

    ASSERT_ENTRY_VALUES(table_dump, "default", "foo:dimen/second", e1);
    ASSERT_ENTRY_VALUES(table_dump, "default", "foo:dimen/third", e2);
    ASSERT_MAP_ENTRY_VALUES(table_dump, "xxhdpi", "foo:style/fourth", style);

    // Deleting the resource should ensure that no ResTable_type for
    // landscape config was emitted. We should still have two
    // ResTable_typeSpec structs though.
    auto built_arsc_file = RedexMappedFile::open(arsc_path);
    auto type_infos = load_types(built_arsc_file);
    EXPECT_EQ(type_infos.size(), 2);
    EXPECT_EQ(type_infos.at(0).configs.size(), 1)
        << "ResTable_type not cleaned up!";
    EXPECT_EQ(type_infos.at(1).configs.size(), 2);
  });
}

TEST(ResTable, SerializeTypeWithAllEmpty) {
  auto pool_flags = android::ResStringPool_header::UTF8_FLAG;
  auto global_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  auto key_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  auto type_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  std::string type_name("dimen");
  type_strings_builder->add_string(type_name.c_str(), type_name.size());

  auto package_builder =
      std::make_shared<arsc::ResPackageBuilder>(&foo_package);
  package_builder->set_key_strings(key_strings_builder);
  package_builder->set_type_strings(type_strings_builder);

  auto table_builder = std::make_shared<arsc::ResTableBuilder>();
  table_builder->set_global_strings(global_strings_builder);
  table_builder->add_package(package_builder);

  // Make a dimen type that is entirely full of empty things
  std::vector<android::ResTable_config*> dimen_configs = {&default_config};
  std::vector<uint32_t> dimen_flags = {0, 0, 0};
  auto dimen_type_definer = std::make_shared<arsc::ResTableTypeDefiner>(
      foo_package.id, 1, dimen_configs, dimen_flags);
  package_builder->add_type(dimen_type_definer);
  dimen_type_definer->add_empty(&default_config);
  dimen_type_definer->add_empty(&default_config);
  dimen_type_definer->add_empty(&default_config);

  // Write to a file, which should be omitting the ResTable_typeSpec and
  // ResTable_type!
  android::Vector<char> out;
  table_builder->serialize(&out);

  auto tmp_dir =
      redex::make_tmp_dir("ResTable_SerializeTypeWithAllEmpty%%%%%%%%");
  auto dest_file_path = tmp_dir.path + "/resources.arsc";
  write_to_file(dest_file_path, out);

  auto build_arsc_file = RedexMappedFile::open(dest_file_path);
  apk::TableParser parser;
  parser.visit((void*)build_arsc_file.const_data(), build_arsc_file.size());
  // Make sure there are no type structures (actual assert is a little wishy
  // washy here cause representing empty data could sensibly be done in
  // different ways).
  EXPECT_TRUE(parser.m_package_types.empty() ||
              parser.m_package_types.begin()->second.empty())
      << "Should not emit type headers for empty data";

  // Final check, make sure Android APIs can parse the table with no types (this
  // will throw with non-zero exit code).
  aapt_dump_and_parse(dest_file_path);
}

namespace {

void build_table_with_ids(const std::string& dest_file_path,
                          bool canonical_entries) {
  auto pool_flags = android::ResStringPool_header::UTF8_FLAG;
  auto global_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  auto key_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  std::vector<std::string> key_strings = {"foo", "name_removed", "bar"};
  for (const auto& s : key_strings) {
    key_strings_builder->add_string(s.c_str(), s.size());
  }
  auto type_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  std::string type_name("id");
  type_strings_builder->add_string(type_name.c_str(), type_name.size());

  auto package_builder =
      std::make_shared<arsc::ResPackageBuilder>(&foo_package);
  package_builder->set_key_strings(key_strings_builder);
  package_builder->set_type_strings(type_strings_builder);

  auto table_builder = std::make_shared<arsc::ResTableBuilder>();
  table_builder->set_global_strings(global_strings_builder);
  table_builder->add_package(package_builder);

  // Make a dimen type that is entirely full of empty things
  std::vector<android::ResTable_config*> id_configs = {&default_config};
  std::vector<uint32_t> flags = {0, 0, 0, 0, 0, 0};
  auto type_definer = std::make_shared<arsc::ResTableTypeDefiner>(
      foo_package.id, 1, id_configs, flags, canonical_entries);
  package_builder->add_type(type_definer);
  type_definer->add(&default_config, &id_0);
  // When canonical_entries is true, following three items will generate three
  // offsets that point to just a single copy of the 16 entry/value bytes.
  type_definer->add(&default_config, &id_1);
  type_definer->add(&default_config, &id_1);
  type_definer->add(&default_config, &id_1);
  // When canonical_entries is true, following two items will generate two
  // offsets that point to just a single copy of the 16 entry/value bytes.
  type_definer->add(&default_config, &id_2);
  type_definer->add(&default_config, &id_2);

  android::Vector<char> out;
  table_builder->serialize(&out);
  write_to_file(dest_file_path, out);
}

} // namespace

TEST(ResTable, ValueEquality) {
  auto pool_flags = android::ResStringPool_header::UTF8_FLAG;
  auto global_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  auto key_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  auto type_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  key_strings_builder->add_string("foo");
  type_strings_builder->add_string("dimen");

  auto package_builder =
      std::make_shared<arsc::ResPackageBuilder>(&foo_package);
  package_builder->set_key_strings(key_strings_builder);
  package_builder->set_type_strings(type_strings_builder);

  auto table_builder = std::make_shared<arsc::ResTableBuilder>();
  table_builder->set_global_strings(global_strings_builder);
  table_builder->add_package(package_builder);

  std::vector<android::ResTable_config*> dimen_configs = {
      &default_config, &land_config, &xxhdpi_config};
  std::vector<uint32_t> dimen_flags = {0, 0, 0, 0, 0};
  auto type_definer = std::make_shared<arsc::ResTableTypeDefiner>(
      foo_package.id, 1, dimen_configs, dimen_flags);
  package_builder->add_type(type_definer);

  EntryAndValue a(0, android::Res_value::TYPE_INT_COLOR_RGB8, 123456);
  EntryAndValue b(0, android::Res_value::TYPE_INT_COLOR_ARGB8, 123456);

  EntryAndValue x(0, android::Res_value::TYPE_DIMENSION, 100);
  EntryAndValue y(0, android::Res_value::TYPE_FRACTION, 200);
  EntryAndValue z(0, android::Res_value::TYPE_INT_DEC, 666);

  // 0x7f010000
  type_definer->add_empty(&default_config);
  type_definer->add(&land_config, &a);
  type_definer->add(&xxhdpi_config, &b);
  // 0x7f010001, make it the reverse of the above (not equal)
  type_definer->add_empty(&default_config);
  type_definer->add(&land_config, &b);
  type_definer->add(&xxhdpi_config, &a);
  // 0x7f010002
  type_definer->add(&default_config, &x);
  type_definer->add(&land_config, &y);
  type_definer->add(&xxhdpi_config, &z);
  // 0x7f010003
  type_definer->add_empty(&default_config);
  type_definer->add(&land_config, &b);
  type_definer->add_empty(&xxhdpi_config);
  // 0x7f010004, should be equal to 0x7f010002
  type_definer->add(&default_config, &x);
  type_definer->add(&land_config, &y);
  type_definer->add(&xxhdpi_config, &z);

  android::Vector<char> out;
  table_builder->serialize(&out);

  auto tmp_dir = redex::make_tmp_dir("ResTable_ValueEquality%%%%%%%%");
  auto dest_file_path = tmp_dir.path + "/resources.arsc";
  write_to_file(dest_file_path, out);
  ResourcesArscFile arsc_file(dest_file_path);

  // Obvious sanity checks
  EXPECT_TRUE(arsc_file.resource_value_identical(0x7f010000, 0x7f010000));
  EXPECT_TRUE(arsc_file.resource_value_identical(0x7f010001, 0x7f010001));
  EXPECT_TRUE(arsc_file.resource_value_identical(0x7f010002, 0x7f010002));
  EXPECT_TRUE(arsc_file.resource_value_identical(0x7f010003, 0x7f010003));
  EXPECT_TRUE(arsc_file.resource_value_identical(0x7f010004, 0x7f010004));

  // Real checks
  EXPECT_TRUE(arsc_file.resource_value_identical(0x7f010002, 0x7f010004));

  EXPECT_FALSE(arsc_file.resource_value_identical(0x7f010000, 0x7f010001));
  EXPECT_FALSE(arsc_file.resource_value_identical(0x7f010000, 0x7f010002));
  EXPECT_FALSE(arsc_file.resource_value_identical(0x7f010001, 0x7f010002));
  EXPECT_FALSE(arsc_file.resource_value_identical(0x7f010002, 0x7f010003));
}

TEST(ResTable, CanonicalEntryData) {
  auto do_validation = [&](const std::string& file_path,
                           uint32_t type_expected_size) {
    auto table_dump = aapt_dump_and_parse(file_path);
    ASSERT_ID_VALUES(table_dump, "default", 0x7f010000, id_0);
    ASSERT_ID_VALUES(table_dump, "default", 0x7f010001, id_1);
    ASSERT_ID_VALUES(table_dump, "default", 0x7f010002, id_1);
    ASSERT_ID_VALUES(table_dump, "default", 0x7f010003, id_1);
    ASSERT_ID_VALUES(table_dump, "default", 0x7f010004, id_2);
    ASSERT_ID_VALUES(table_dump, "default", 0x7f010005, id_2);

    // Assert the size of the ResTable_type for the id table
    auto file = RedexMappedFile::open(file_path);
    apk::TableParser parser;
    parser.visit((void*)file.const_data(), file.size());
    auto type_info = parser.m_package_types.begin()->second.at(0);
    EXPECT_EQ(type_info.configs.at(0)->header.size, type_expected_size)
        << "ResTable_type was not expected size!";
  };

  uint32_t num_entries = 6;
  uint32_t expected_size_no_canon = sizeof(android::ResTable_type) +
                                    num_entries * sizeof(uint32_t) +
                                    num_entries * sizeof(EntryAndValue);
  // Three of these things are duplicated, but the offsets will remain.
  uint32_t expected_size_canon =
      expected_size_no_canon - 3 * sizeof(EntryAndValue);

  auto tmp_dir = redex::make_tmp_dir("ResTable_CanonicalEntryData%%%%%%%%");

  auto no_canonical_path = tmp_dir.path + "/no_canon.arsc";
  build_table_with_ids(no_canonical_path, false);
  do_validation(no_canonical_path, expected_size_no_canon);

  auto canonical_path = tmp_dir.path + "/canon.arsc";
  build_table_with_ids(canonical_path, true);
  do_validation(canonical_path, expected_size_canon);

  // Make sure the Projector also emits the data properly in canonical form. Do
  // this by taking the non-canonical table, as prepared by the Definer, and
  // copy it all into a new builder using the Projector with the
  // "enable_canonical_entries" flag flipped to true.
  auto no_canon_file = RedexMappedFile::open(no_canonical_path);
  apk::TableParser parsed_table;
  parsed_table.visit((void*)no_canon_file.const_data(), no_canon_file.size());
  auto package_builder =
      std::make_shared<arsc::ResPackageBuilder>(&foo_package);
  package_builder->set_key_strings(
      parsed_table.m_package_key_string_headers.begin()->second);
  package_builder->set_type_strings(
      parsed_table.m_package_type_string_headers.begin()->second);

  auto& id_type = parsed_table.m_package_types.begin()->second.at(0);
  auto type_projector = std::make_shared<arsc::ResTableTypeProjector>(
      foo_package.id, id_type.spec, id_type.configs, true);
  package_builder->add_type(type_projector);

  auto table_builder = std::make_shared<arsc::ResTableBuilder>();
  table_builder->set_global_strings(parsed_table.m_global_pool_header);
  table_builder->add_package(package_builder);
  android::Vector<char> out;
  table_builder->serialize(&out);

  auto projector_canon_path = tmp_dir.path + "/projector_canon.arsc";
  write_to_file(projector_canon_path, out);
  do_validation(projector_canon_path, expected_size_canon);
}

TEST(ResTable, GetStringsByName) {
  // Make the strangest hypothetical table that could exist (string with values
  // in multiple configs, references with cycles, etc).
  auto pool_flags = android::ResStringPool_header::UTF8_FLAG;
  auto global_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  global_strings_builder->add_string("a");
  global_strings_builder->add_string("b");
  global_strings_builder->add_string("c");
  global_strings_builder->add_string("d");
  global_strings_builder->add_string("e");
  auto key_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  key_strings_builder->add_string("first");
  key_strings_builder->add_string("second");
  key_strings_builder->add_string("third");
  key_strings_builder->add_string("fourth");
  auto type_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  type_strings_builder->add_string("string");

  auto package_builder =
      std::make_shared<arsc::ResPackageBuilder>(&foo_package);
  package_builder->set_key_strings(key_strings_builder);
  package_builder->set_type_strings(type_strings_builder);

  auto table_builder = std::make_shared<arsc::ResTableBuilder>();
  table_builder->set_global_strings(global_strings_builder);
  table_builder->add_package(package_builder);

  std::vector<android::ResTable_config*> string_configs = {&default_config,
                                                           &land_config};
  std::vector<uint32_t> string_flags = {
      0, android::ResTable_config::CONFIG_ORIENTATION,
      android::ResTable_config::CONFIG_ORIENTATION,
      android::ResTable_config::CONFIG_ORIENTATION};
  auto type_definer = std::make_shared<arsc::ResTableTypeDefiner>(
      foo_package.id, 1, string_configs, string_flags);
  package_builder->add_type(type_definer);

  EntryAndValue first(0, android::Res_value::TYPE_STRING, 0);
  type_definer->add(&default_config, &first);
  type_definer->add_empty(&land_config);

  EntryAndValue second(1, android::Res_value::TYPE_STRING, 1);
  EntryAndValue second_land(1, android::Res_value::TYPE_STRING, 2);
  type_definer->add(&default_config, &second);
  type_definer->add(&land_config, &second_land);

  // These next two entries are diabolical as some of their entries will be
  // cyclic.
  EntryAndValue third(2, android::Res_value::TYPE_STRING, 3);
  EntryAndValue third_land(2, android::Res_value::TYPE_REFERENCE, 0x7f010003);
  type_definer->add(&default_config, &third);
  type_definer->add(&land_config, &third_land);

  EntryAndValue fourth(3, android::Res_value::TYPE_REFERENCE, 0x7f010002);
  EntryAndValue fourth_land(3, android::Res_value::TYPE_STRING, 4);
  type_definer->add(&default_config, &fourth);
  type_definer->add(&land_config, &fourth_land);

  android::Vector<char> out;
  table_builder->serialize(&out);

  auto tmp_dir = redex::make_tmp_dir("ResTable_GetStringsByName%%%%%%%%");
  auto arsc_path = tmp_dir.path + "/resources.arsc";
  write_to_file(arsc_path, out);

  ResourcesArscFile arsc_file(arsc_path);
  auto vec = arsc_file.get_resource_strings_by_name("first");
  EXPECT_EQ(vec.size(), 1);
  EXPECT_STREQ(vec[0].c_str(), "a");
  vec = arsc_file.get_resource_strings_by_name("second");
  EXPECT_EQ(vec.size(), 2);
  EXPECT_STREQ(vec[0].c_str(), "b");
  EXPECT_STREQ(vec[1].c_str(), "c");
  vec = arsc_file.get_resource_strings_by_name("third");
  EXPECT_EQ(vec.size(), 2);
  EXPECT_STREQ(vec[0].c_str(), "d");
  EXPECT_STREQ(vec[1].c_str(), "e");
  vec = arsc_file.get_resource_strings_by_name("blah");
  EXPECT_EQ(vec.size(), 0);
}

TEST(ResTable, BuildDumpAndParseSparseType) {
  // Build a table with one type (dimen) with two configurations, the latter
  // being sparsely encoded. Default config will have all values for all entries
  // but land config will have values for only second and fourth.
  auto pool_flags = android::ResStringPool_header::UTF8_FLAG;
  auto global_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  auto key_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  key_strings_builder->add_string("first");
  key_strings_builder->add_string("second");
  key_strings_builder->add_string("third");
  key_strings_builder->add_string("fourth");
  key_strings_builder->add_string("fifth");
  key_strings_builder->add_string("sixth");
  key_strings_builder->add_string("seventh");
  key_strings_builder->add_string("eighth");
  auto type_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  type_strings_builder->add_string("dimen");

  auto package_builder =
      std::make_shared<arsc::ResPackageBuilder>(&foo_package);
  package_builder->set_key_strings(key_strings_builder);
  package_builder->set_type_strings(type_strings_builder);

  auto table_builder = std::make_shared<arsc::ResTableBuilder>();
  table_builder->set_global_strings(global_strings_builder);
  table_builder->add_package(package_builder);

  std::vector<android::ResTable_config*> dimen_configs = {&default_config,
                                                          &land_config};
  std::vector<uint32_t> flags = {
      0, android::ResTable_config::CONFIG_ORIENTATION,
      0, android::ResTable_config::CONFIG_ORIENTATION,
      0, 0,
      0, 0};
  auto type_definer = std::make_shared<arsc::ResTableTypeDefiner>(
      foo_package.id, 1, dimen_configs, flags, true, true);
  package_builder->add_type(type_definer);

  // Add all 8 values
  uint32_t key_idx = 0;
  EntryAndValue first(key_idx++, android::Res_value::TYPE_DIMENSION, 1);
  type_definer->add(&default_config, &first);
  type_definer->add_empty(&land_config);

  EntryAndValue second(key_idx, android::Res_value::TYPE_DIMENSION, 2);
  EntryAndValue second_land(key_idx++, android::Res_value::TYPE_DIMENSION, 22);
  type_definer->add(&default_config, &second);
  type_definer->add(&land_config, &second_land);

  EntryAndValue third(key_idx++, android::Res_value::TYPE_DIMENSION, 3);
  type_definer->add(&default_config, &third);
  type_definer->add_empty(&land_config);

  EntryAndValue fourth(key_idx, android::Res_value::TYPE_DIMENSION, 4);
  EntryAndValue fourth_land(key_idx++, android::Res_value::TYPE_DIMENSION, 44);
  type_definer->add(&default_config, &fourth);
  type_definer->add(&land_config, &fourth_land);

  EntryAndValue fifth(key_idx++, android::Res_value::TYPE_DIMENSION, 5);
  type_definer->add(&default_config, &fifth);
  type_definer->add_empty(&land_config);

  EntryAndValue sixth(key_idx++, android::Res_value::TYPE_DIMENSION, 6);
  type_definer->add(&default_config, &sixth);
  type_definer->add_empty(&land_config);

  EntryAndValue seventh(key_idx++, android::Res_value::TYPE_DIMENSION, 7);
  type_definer->add(&default_config, &seventh);
  type_definer->add_empty(&land_config);

  EntryAndValue eighth(key_idx++, android::Res_value::TYPE_DIMENSION, 8);
  type_definer->add(&default_config, &eighth);
  type_definer->add_empty(&land_config);

  android::Vector<char> out;
  table_builder->serialize(&out);

  auto tmp_dir = redex::make_tmp_dir("ResTable_Sparse%%%%%%%%");
  auto arsc_path = tmp_dir.path + "/resources.arsc";
  write_to_file(arsc_path, out);

  size_t size_of_entry_value_and_offset =
      sizeof(uint32_t) + sizeof(EntryAndValue);
  {
    // Make sure aapt can parse it.
    auto round_trip_dump = aapt_dump_and_parse(arsc_path);
    EXPECT_EQ(round_trip_dump.get_simple_value("default", 0x7f010001).data, 2);
    EXPECT_EQ(round_trip_dump.get_simple_value("land", 0x7f010001).data, 22);
    EXPECT_EQ(round_trip_dump.get_simple_value("default", 0x7f010003).data, 4);
    EXPECT_EQ(round_trip_dump.get_simple_value("land", 0x7f010003).data, 44);

    // Make sure our APIs can still parse it.
    ResourcesArscFile arsc_file(arsc_path);
    EXPECT_EQ(arsc_file.resource_value_count(0x7f010000), 1);
    EXPECT_EQ(arsc_file.resource_value_count(0x7f010001), 2);
    EXPECT_EQ(arsc_file.resource_value_count(0x7f010002), 1);
    EXPECT_EQ(arsc_file.resource_value_count(0x7f010003), 2);
    EXPECT_EQ(arsc_file.resource_value_count(0x7f010004), 1);
    EXPECT_EQ(arsc_file.resource_value_count(0x7f010005), 1);
    EXPECT_EQ(arsc_file.resource_value_count(0x7f010006), 1);
    EXPECT_EQ(arsc_file.resource_value_count(0x7f010007), 1);

    // Ensure sparse encoding really took effect, by checking the resulting data
    // size.
    auto& parsed_table = arsc_file.get_table_snapshot().get_parsed_table();
    auto& types = parsed_table.m_types_to_configs.begin()->second;
    EXPECT_EQ(types.size(), 2);
    EXPECT_EQ(
        types.at(0)->header.size,
        sizeof(android::ResTable_type) + 8 * size_of_entry_value_and_offset);
    EXPECT_EQ(
        types.at(1)->header.size,
        sizeof(android::ResTable_type) + 2 * size_of_entry_value_and_offset)
        << "Expected small type size due to sparse encoding";

    // Deleting an entry will force ResTableTypeProjector to parse/emit the
    // sparse encoding. More validation will happen on the resulting file to
    // ensure things are well formed.
    arsc_file.delete_resource(0x7f010001);
    arsc_file.serialize();
  }
  {
    // Make sure aapt can parse it.
    auto round_trip_dump = aapt_dump_and_parse(arsc_path);
    EXPECT_EQ(round_trip_dump.get_simple_value("default", 0x7f010001).data, 3);
    EXPECT_EQ(round_trip_dump.get_simple_value("default", 0x7f010002).data, 4);
    EXPECT_EQ(round_trip_dump.get_simple_value("land", 0x7f010002).data, 44);

    // Make sure our APIs can still parse it.
    ResourcesArscFile arsc_file(arsc_path);
    EXPECT_EQ(arsc_file.resource_value_count(0x7f010000), 1);
    EXPECT_EQ(arsc_file.resource_value_count(0x7f010001), 1);
    // Post-delete, 0x7f010002 is now entry "fourth" which is the only one with
    // values in two configs.
    EXPECT_EQ(arsc_file.resource_value_count(0x7f010002), 2);
    EXPECT_EQ(arsc_file.resource_value_count(0x7f010003), 1);
    EXPECT_EQ(arsc_file.resource_value_count(0x7f010004), 1);
    EXPECT_EQ(arsc_file.resource_value_count(0x7f010005), 1);
    EXPECT_EQ(arsc_file.resource_value_count(0x7f010006), 1);

    // Ensure sparse encoding really took effect, by checking the resulting data
    // size.
    auto& parsed_table = arsc_file.get_table_snapshot().get_parsed_table();
    auto& types = parsed_table.m_types_to_configs.begin()->second;
    EXPECT_EQ(types.size(), 2);
    EXPECT_EQ(
        types.at(0)->header.size,
        sizeof(android::ResTable_type) + 7 * size_of_entry_value_and_offset);
    EXPECT_EQ(types.at(1)->header.size,
              sizeof(android::ResTable_type) + size_of_entry_value_and_offset)
        << "Expected small type size due to sparse encoding";
    // Additional validation that ResTableTypeProjector filled out headers
    // properly.
    EXPECT_EQ(parsed_table.m_types.begin()->second->res1, 2)
        << "First ResTable_typeSpec should specify two ResTable_type structs "
           "to follow";
  }
}

constexpr uint32_t WRAP_CONTENT = 0xFFFFFFFE;
// A class which will mess around with the first Button element it finds, wiping
// out all its attributes and replacing them with layout_width/layout_height.
class ButtonFiddler : public arsc::SimpleXmlParser {
 public:
  explicit ButtonFiddler(arsc::ResFileManipulator* file_manupulator)
      : m_file_manipulator(file_manupulator) {}

  bool find_string_idx(const std::string& target, uint32_t* out_idx) {
    auto& string_pool = global_strings();
    for (size_t i = 0; i < string_pool.size(); i++) {
      auto s = arsc::get_string_from_pool(string_pool, i);
      if (s == target) {
        *out_idx = i;
        return true;
      }
    }
    return false;
  }

  bool visit_start_tag(android::ResXMLTree_node* node,
                       android::ResXMLTree_attrExt* extension) override {
    auto& string_pool = global_strings();
    auto element_name =
        arsc::get_string_from_pool(string_pool, dtohl(extension->name.index));
    if (element_name == "Button") {
      // Read some basic info about the file's strings. We will simply reuse
      // existing string data and clobber the node and its attributes down to a
      // node with only layout_width and layout_height (both set to
      // "wrap_content").
      uint32_t layout_width_idx;
      uint32_t layout_height_idx;
      uint32_t uri_idx;
      always_assert_log(find_string_idx("layout_width", &layout_width_idx),
                        "pool did not have expected width string");
      always_assert_log(find_string_idx("layout_height", &layout_height_idx),
                        "pool did not have expected height string");
      always_assert_log(
          find_string_idx("http://schemas.android.com/apk/res/android",
                          &uri_idx),
          "pool did not have expected uri string");

      // Build up all the structs we will be inserting
      auto new_data_size = sizeof(android::ResXMLTree_node) +
                           sizeof(android::ResXMLTree_attrExt) +
                           2 * sizeof(android::ResXMLTree_attribute);
      arsc::ResFileManipulator::Block block(new_data_size);

      // Start filling out the data. Copy existing and change small details.
      android::ResXMLTree_node new_node = *node;
      new_node.header.size = new_data_size;
      block.write(new_node);

      android::ResXMLTree_attrExt new_extension = *extension;
      new_extension.attributeCount = 2;
      new_extension.idIndex = 0;
      new_extension.classIndex = 0;
      new_extension.styleIndex = 0;
      block.write(new_extension);

      // Attributes are built up from scratch.
      android::ResXMLTree_attribute layout_width;
      layout_width.ns.index = uri_idx;
      layout_width.name.index = layout_width_idx;
      layout_width.rawValue.index = 0xFFFFFFFF;
      layout_width.typedValue.size = sizeof(android::Res_value);
      layout_width.typedValue.dataType = android::Res_value::TYPE_INT_DEC;
      layout_width.typedValue.data = WRAP_CONTENT;
      block.write(layout_width);

      android::ResXMLTree_attribute layout_height;
      layout_height.ns.index = uri_idx;
      layout_height.name.index = layout_height_idx;
      layout_height.rawValue.index = 0xFFFFFFFF;
      layout_height.typedValue.size = sizeof(android::Res_value);
      layout_height.typedValue.dataType = android::Res_value::TYPE_INT_DEC;
      layout_height.typedValue.data = WRAP_CONTENT;
      block.write(layout_height);

      m_file_manipulator->add_at(node, std::move(block));
      m_file_manipulator->delete_at(node, dtohl(node->header.size));
      m_changes++;
    }
    return arsc::SimpleXmlParser::visit_start_tag(node, extension);
  }

  size_t changes() { return m_changes; }

  arsc::ResFileManipulator* m_file_manipulator;
  size_t m_changes{0};
};

class AttributeCounter : public arsc::SimpleXmlParser {
 public:
  bool visit_start_tag(android::ResXMLTree_node* node,
                       android::ResXMLTree_attrExt* extension) override {
    return arsc::SimpleXmlParser::visit_start_tag(node, extension);
  }

  bool visit_attribute(android::ResXMLTree_node* node,
                       android::ResXMLTree_attrExt* extension,
                       android::ResXMLTree_attribute* attribute) override {
    m_attributes++;
    auto& string_pool = global_strings();
    auto attribute_name =
        arsc::get_string_from_pool(string_pool, dtohl(attribute->name.index));
    m_found_attributes.emplace(attribute_name);
    return arsc::SimpleXmlParser::visit_attribute(node, extension, attribute);
  }

  size_t m_attributes{0};
  std::unordered_set<std::string> m_found_attributes;
};

TEST(FileManipulator, RebuildXmlFile) {
  auto path = get_env("xml_path");
  auto f = RedexMappedFile::open(path);
  auto* data = (char*)f.const_data();

  arsc::ResFileManipulator file_manipulator(data, f.size());
  ButtonFiddler fiddler(&file_manipulator);
  fiddler.visit(data, f.size());
  EXPECT_EQ(fiddler.changes(), 1);
  android::Vector<char> serialized;
  file_manipulator.serialize(&serialized);

  // Read the serialized data with visitor API, should encounter start/end tag
  // with two attributes.
  AttributeCounter counter;
  EXPECT_TRUE(counter.visit((char*)serialized.array(), serialized.size()));
  EXPECT_EQ(counter.m_attributes, 2);
  EXPECT_EQ(counter.m_found_attributes.count("layout_width"), 1);
  EXPECT_EQ(counter.m_found_attributes.count("layout_height"), 1);
}

TEST(FileManipulator, AppendAtEnd) {
  auto path = get_env("xml_path");
  auto f = RedexMappedFile::open(path);
  auto* data_ptr = (char*)f.const_data();

  arsc::ResFileManipulator file_manipulator(data_ptr, f.size());
  android::ResChunk_header new_data;
  new_data.type = 0x99;
  new_data.headerSize = sizeof(android::ResChunk_header);
  new_data.size = 0xCCCC;
  // append at the very end of the file, make sure it actually works.
  for (size_t i = 0; i < f.size(); i++, data_ptr++) {
    // simulate doing something...
    if (i % 10 == 0) {
      file_manipulator.replace_at(data_ptr, *data_ptr);
    }
  }
  file_manipulator.add_at(data_ptr, new_data);

  android::Vector<char> serialized;
  file_manipulator.serialize(&serialized);
}

TEST(Xml, AttributeSorting) {
  constexpr uint32_t ATTR_ID_COUNT = 4;
  // Fictional string pool
  std::vector<std::string> strings = {
      "id",
      "layout_width",
      "layout_height",
      "layout_margin",
      "Button",
      "yolo",
      "class",
      "style",
      "http://schemas.android.com/apk/res/android"};
  constexpr uint32_t ID_INDEX = 0;
  constexpr uint32_t LAYOUT_WIDTH_INDEX = 1;
  constexpr uint32_t LAYOUT_HEIGHT_INDEX = 2;
  constexpr uint32_t LAYOUT_MARGIN_INDEX = 3;
  constexpr uint32_t BUTTON_INDEX = 4;
  constexpr uint32_t YOLO_INDEX = 5;
  constexpr uint32_t CLASS_INDEX = 6;
  constexpr uint32_t STYLE_INDEX = 7;
  constexpr uint32_t URI_INDEX = 8;
  auto pool_lookup = [&](uint32_t idx) { return strings.at(idx); };

  auto extension_and_attributes_size =
      sizeof(android::ResXMLTree_attrExt) +
      4 * sizeof(android::ResXMLTree_attribute);

  android::ResXMLTree_node node{};
  node.header.type = android::RES_XML_START_ELEMENT_TYPE;
  node.header.headerSize = sizeof(android::ResXMLTree_node);
  node.header.size =
      sizeof(android::ResXMLTree_node) + extension_and_attributes_size;
  node.comment.index = arsc::NO_VALUE;
  node.lineNumber = 2;

  // Set up fictional xml element data with 4 sequential attributes
  arsc::ResFileManipulator::Block block(extension_and_attributes_size);
  android::ResXMLTree_attrExt extension{};
  extension.attributeCount = 4;
  extension.attributeSize = sizeof(android::ResXMLTree_attribute);
  extension.attributeStart = sizeof(android::ResXMLTree_attrExt);
  extension.ns.index = arsc::NO_VALUE;
  extension.idIndex = 0;
  extension.classIndex = 0;
  extension.styleIndex = 3;
  block.write(extension);

  // Attributes
  android::ResXMLTree_attribute layout_width;
  layout_width.ns.index = URI_INDEX;
  layout_width.name.index = LAYOUT_WIDTH_INDEX;
  layout_width.rawValue.index = arsc::NO_VALUE;
  layout_width.typedValue.size = sizeof(android::Res_value);
  layout_width.typedValue.dataType = android::Res_value::TYPE_INT_DEC;
  layout_width.typedValue.data = WRAP_CONTENT;
  block.write(layout_width);

  android::ResXMLTree_attribute layout_height;
  layout_height.ns.index = URI_INDEX;
  layout_height.name.index = LAYOUT_HEIGHT_INDEX;
  layout_height.rawValue.index = arsc::NO_VALUE;
  layout_height.typedValue.size = sizeof(android::Res_value);
  layout_height.typedValue.dataType = android::Res_value::TYPE_INT_DEC;
  layout_height.typedValue.data = WRAP_CONTENT;
  block.write(layout_height);

  android::ResXMLTree_attribute layout_margin;
  layout_margin.ns.index = URI_INDEX;
  layout_margin.name.index = LAYOUT_MARGIN_INDEX;
  layout_margin.rawValue.index = arsc::NO_VALUE;
  layout_margin.typedValue.size = sizeof(android::Res_value);
  layout_margin.typedValue.dataType = android::Res_value::TYPE_INT_DEC;
  layout_margin.typedValue.data = 123;
  block.write(layout_margin);

  android::ResXMLTree_attribute style_attr;
  style_attr.ns.index = arsc::NO_VALUE;
  style_attr.name.index = STYLE_INDEX;
  style_attr.rawValue.index = arsc::NO_VALUE;
  style_attr.typedValue.size = sizeof(android::Res_value);
  style_attr.typedValue.dataType = android::Res_value::TYPE_REFERENCE;
  style_attr.typedValue.data = 0x7f050005;
  block.write(style_attr);

  auto* extension_ptr = (android::ResXMLTree_attrExt*)block.buffer.get();

  // class attribute should come before style.
  {
    android::ResXMLTree_attribute class_attr;
    class_attr.ns.index = arsc::NO_VALUE;
    class_attr.name.index = CLASS_INDEX;
    class_attr.rawValue.index = BUTTON_INDEX;
    class_attr.typedValue.size = sizeof(android::Res_value);
    class_attr.typedValue.dataType = android::Res_value::TYPE_STRING;
    class_attr.typedValue.data = BUTTON_INDEX;
    EXPECT_EQ(arsc::find_attribute_ordinal(&node, extension_ptr, &class_attr,
                                           ATTR_ID_COUNT, pool_lookup),
              3);
  }
  // id attribute should come before all others (id has a lower id, lol).
  {
    android::ResXMLTree_attribute id_attr;
    id_attr.ns.index = URI_INDEX;
    id_attr.name.index = ID_INDEX;
    id_attr.rawValue.index = arsc::NO_VALUE;
    id_attr.typedValue.size = sizeof(android::Res_value);
    id_attr.typedValue.dataType = android::Res_value::TYPE_REFERENCE;
    id_attr.typedValue.data = 0x7f030001;
    EXPECT_EQ(arsc::find_attribute_ordinal(&node, extension_ptr, &id_attr,
                                           ATTR_ID_COUNT, pool_lookup),
              0);
  }
  // This is a fictional attribute, but should exercise this un-namespaced
  // attribute coming last
  {
    android::ResXMLTree_attribute yolo;
    yolo.ns.index = arsc::NO_VALUE;
    yolo.name.index = YOLO_INDEX;
    // yolo="yolo" of course
    yolo.rawValue.index = YOLO_INDEX;
    yolo.typedValue.size = sizeof(android::Res_value);
    yolo.typedValue.dataType = android::Res_value::TYPE_STRING;
    yolo.typedValue.data = YOLO_INDEX;
    EXPECT_EQ(arsc::find_attribute_ordinal(&node, extension_ptr, &yolo,
                                           ATTR_ID_COUNT, pool_lookup),
              4);
  }
}

TEST(ResTableTypeDefiner, AddComplexEntryBuilder) {
  auto pool_flags = android::ResStringPool_header::UTF8_FLAG;
  auto global_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  auto key_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);
  auto type_strings_builder =
      std::make_shared<arsc::ResStringPoolBuilder>(pool_flags);

  key_strings_builder->add_string("test_entry");
  type_strings_builder->add_string("style");

  auto package_builder =
      std::make_shared<arsc::ResPackageBuilder>(&foo_package);
  package_builder->set_key_strings(key_strings_builder);
  package_builder->set_type_strings(type_strings_builder);

  auto table_builder = std::make_shared<arsc::ResTableBuilder>();
  table_builder->set_global_strings(global_strings_builder);
  table_builder->add_package(package_builder);

  std::vector<android::ResTable_config*> configs = {&default_config,
                                                    &land_config};
  std::vector<uint32_t> flags = {android::ResTable_config::CONFIG_ORIENTATION};

  auto type_definer = std::make_shared<arsc::ResTableTypeDefiner>(
      foo_package.id, 1, configs, flags);
  package_builder->add_type(type_definer);

  arsc::ResComplexEntryBuilder default_builder;
  default_builder.set_key_string_index(0);
  default_builder.set_parent_id(0);
  default_builder.add(0x01010098, android::Res_value::TYPE_INT_COLOR_RGB8,
                      0xFF0000FF);
  default_builder.add(0x010100d4, android::Res_value::TYPE_INT_COLOR_RGB8,
                      0xFF00FF00);

  arsc::ResComplexEntryBuilder land_builder;
  land_builder.set_key_string_index(0);
  land_builder.set_parent_id(0);
  land_builder.add(0x01010098, android::Res_value::TYPE_INT_COLOR_RGB8,
                   0xFFFF0000);
  land_builder.add(0x010100d4, android::Res_value::TYPE_INT_COLOR_RGB8,
                   0xFF000000);

  type_definer->add(&default_config, default_builder);
  type_definer->add(&land_config, land_builder);

  android::Vector<char> serialized;
  table_builder->serialize(&serialized);

  auto tmp_dir =
      redex::make_tmp_dir("ResTableTypeDefiner_AddComplexEntryBuilder%%%%%%%%");
  auto arsc_path = tmp_dir.path + "/resources.arsc";
  write_to_file(arsc_path, serialized);

  auto table_dump = aapt_dump_and_parse(arsc_path);

  uint32_t resource_id = 0x7f010000;

  auto default_values = table_dump.get_complex_values("default", resource_id);
  ASSERT_EQ(default_values.size(), 2);
  EXPECT_EQ(default_values[0].key, 0x01010098);
  EXPECT_EQ(default_values[0].data, 0xFF0000FF);
  EXPECT_EQ(default_values[1].key, 0x010100d4);
  EXPECT_EQ(default_values[1].data, 0xFF00FF00);

  auto land_values = table_dump.get_complex_values("land", resource_id);
  ASSERT_EQ(land_values.size(), 2);
  EXPECT_EQ(land_values[0].key, 0x01010098);
  EXPECT_EQ(land_values[0].data, 0xFFFF0000);
  EXPECT_EQ(land_values[1].key, 0x010100d4);
  EXPECT_EQ(land_values[1].data, 0xFF000000);
}

TEST(ResourcesArscFile, ApplyAttributeRemovals) {
  build_arsc_file_and_validate([&](const std::string& /* unused */,
                                   const std::string& arsc_path) {
    auto initial_dump = aapt_dump_and_parse(arsc_path);
    uint32_t resource_id = 0x7f020000;
    auto initial_values =
        initial_dump.get_complex_values("xxhdpi", resource_id);
    ASSERT_EQ(initial_values.size(), 2);
    EXPECT_EQ(initial_values[0].key, 0x01010098);
    EXPECT_EQ(initial_values[1].key, 0x010100d4);

    {
      ResourcesArscFile arsc_file(arsc_path);
      std::vector<resources::StyleModificationSpec::Modification> modifications;
      resources::StyleModificationSpec::Modification mod{resource_id,
                                                         0x010100d4};
      modifications.push_back(mod);

      arsc_file.apply_attribute_removals_and_additions(modifications, {});
    }
    {
      auto modified_dump = aapt_dump_and_parse(arsc_path);
      auto modified_values =
          modified_dump.get_complex_values("xxhdpi", resource_id);
      ASSERT_EQ(modified_values.size(), 1);
      EXPECT_EQ(modified_values[0].key, 0x01010098);
    }

    {
      ResourcesArscFile arsc_file(arsc_path);
      std::vector<resources::StyleModificationSpec::Modification> modifications;
      resources::StyleModificationSpec::Modification mod{resource_id,
                                                         0x01010000};
      modifications.push_back(mod);

      arsc_file.apply_attribute_removals_and_additions(modifications, {});
    }
    {
      auto modified_dump = aapt_dump_and_parse(arsc_path);
      auto modified_values =
          modified_dump.get_complex_values("xxhdpi", resource_id);
      ASSERT_EQ(modified_values.size(), 1);
      EXPECT_EQ(modified_values[0].key, 0x01010098);
    }

    {
      ResourcesArscFile arsc_file(arsc_path);
      std::vector<resources::StyleModificationSpec::Modification> modifications;
      resources::StyleModificationSpec::Modification mod{0x7f030000,
                                                         0x01010098};
      modifications.push_back(mod);

      arsc_file.apply_attribute_removals_and_additions(modifications, {});
    }
    {
      auto modified_dump = aapt_dump_and_parse(arsc_path);
      auto modified_values =
          modified_dump.get_complex_values("xxhdpi", resource_id);
      ASSERT_EQ(modified_values.size(), 1);
      EXPECT_EQ(modified_values[0].key, 0x01010098);
    }

    {
      ResourcesArscFile arsc_file(arsc_path);
      std::vector<resources::StyleModificationSpec::Modification> modifications;

      resources::StyleModificationSpec::Modification mod1{resource_id,
                                                          0x01010098};
      resources::StyleModificationSpec::Modification mod2{resource_id,
                                                          0x01010099};
      modifications.push_back(mod1);
      modifications.push_back(mod2);

      arsc_file.apply_attribute_removals_and_additions(modifications, {});
    }
    {
      auto final_dump = aapt_dump_and_parse(arsc_path);
      auto final_values = final_dump.get_complex_values("xxhdpi", resource_id);
      ASSERT_EQ(final_values.size(), 0);
    }
  });
}
TEST(ResComplexEntryBuilder, AttributeSorting) {
  arsc::ResComplexEntryBuilder builder;
  builder.set_key_string_index(0);
  builder.set_parent_id(0);

  uint32_t high_attr_id = 0x01010200;
  uint32_t mid_attr_id = 0x01010150;
  uint32_t low_attr_id = 0x01010100;

  builder.add(high_attr_id, android::Res_value::TYPE_INT_COLOR_RGB8,
              0xFF0000FF);
  builder.add(mid_attr_id, android::Res_value::TYPE_INT_COLOR_RGB8, 0xFF00FF00);
  builder.add(low_attr_id, android::Res_value::TYPE_INT_COLOR_RGB8, 0xFFFF0000);

  android::Vector<char> serialized;
  builder.serialize(&serialized);

  ASSERT_GT(serialized.size(), 0);

  android::ResTable_map_entry map_entry;
  std::memcpy(&map_entry, serialized.array(),
              sizeof(android::ResTable_map_entry));
  ASSERT_EQ(map_entry.count, 3);

  std::vector<android::ResTable_map> maps(3);
  std::memcpy(maps.data(),
              serialized.array() + sizeof(android::ResTable_map_entry),
              3 * sizeof(android::ResTable_map));

  EXPECT_EQ(maps[0].name.ident, low_attr_id);
  EXPECT_EQ(maps[1].name.ident, mid_attr_id);
  EXPECT_EQ(maps[2].name.ident, high_attr_id);
}

TEST(ResourcesArscFile, ApplyAttributeAdditions) {
  build_arsc_file_and_validate([&](const std::string& /* unused */,
                                   const std::string& arsc_path) {
    auto initial_dump = aapt_dump_and_parse(arsc_path);
    uint32_t resource_id = 0x7f020000;
    auto initial_values =
        initial_dump.get_complex_values("xxhdpi", resource_id);
    ASSERT_EQ(initial_values.size(), 2);
    EXPECT_EQ(initial_values[0].key, 0x01010098);
    EXPECT_EQ(initial_values[1].key, 0x010100d4);

    {
      ResourcesArscFile arsc_file(arsc_path);
      std::vector<resources::StyleModificationSpec::Modification> modifications;
      resources::StyleModificationSpec::Modification mod{
          resource_id, 0x01010099,
          resources::StyleResource::Value{
              android::Res_value::TYPE_INT_COLOR_RGB8, 0xFFFFFFFF}};
      modifications.push_back(mod);

      arsc_file.apply_attribute_removals_and_additions(modifications, {});
    }
    {
      auto modified_dump = aapt_dump_and_parse(arsc_path);
      auto modified_values =
          modified_dump.get_complex_values("xxhdpi", resource_id);
      ASSERT_EQ(modified_values.size(), 3);
      EXPECT_EQ(modified_values[0].key, 0x01010098);
      EXPECT_EQ(modified_values[1].key, 0x01010099);
      EXPECT_EQ(modified_values[1].data, 0xFFFFFFFF);
      EXPECT_EQ(modified_values[2].key, 0x010100d4);
    }

    {
      ResourcesArscFile arsc_file(arsc_path);
      std::vector<resources::StyleModificationSpec::Modification> modifications;
      resources::StyleModificationSpec::Modification mod1{
          resource_id, 0x01010100,
          resources::StyleResource::Value{
              android::Res_value::TYPE_INT_COLOR_RGB8, 0xFF000000}};
      resources::StyleModificationSpec::Modification mod2{
          resource_id, 0x01010101,
          resources::StyleResource::Value{
              android::Res_value::TYPE_INT_COLOR_RGB8, 0xFF888888}};
      modifications.push_back(mod1);
      modifications.push_back(mod2);

      arsc_file.apply_attribute_removals_and_additions(modifications, {});
    }
    {
      auto final_dump = aapt_dump_and_parse(arsc_path);
      auto final_values = final_dump.get_complex_values("xxhdpi", resource_id);
      ASSERT_EQ(final_values.size(), 5);
      EXPECT_EQ(final_values[0].key, 0x01010098);
      EXPECT_EQ(final_values[1].key, 0x01010099);
      EXPECT_EQ(final_values[2].key, 0x010100d4);
      EXPECT_EQ(final_values[3].key, 0x01010100);
      EXPECT_EQ(final_values[3].data, 0xFF000000);
      EXPECT_EQ(final_values[4].key, 0x01010101);
      EXPECT_EQ(final_values[4].data, 0xFF888888);
    }

    {
      ResourcesArscFile arsc_file(arsc_path);
      std::vector<resources::StyleModificationSpec::Modification> modifications;
      resources::StyleModificationSpec::Modification mod{
          0x7f030000, 0x01010102,
          resources::StyleResource::Value{
              android::Res_value::TYPE_INT_COLOR_RGB8, 0xFF123456}};
      modifications.push_back(mod);

      arsc_file.apply_attribute_removals_and_additions(modifications, {});
    }
    {
      auto unchanged_dump = aapt_dump_and_parse(arsc_path);
      auto unchanged_values =
          unchanged_dump.get_complex_values("xxhdpi", resource_id);
      ASSERT_EQ(unchanged_values.size(), 5);
      EXPECT_EQ(unchanged_values[0].key, 0x01010098);
      EXPECT_EQ(unchanged_values[1].key, 0x01010099);
      EXPECT_EQ(unchanged_values[2].key, 0x010100d4);
      EXPECT_EQ(unchanged_values[3].key, 0x01010100);
      EXPECT_EQ(unchanged_values[4].key, 0x01010101);
    }
  });
}

TEST(ResourcesArscFile, ApplyAttributeRemovalsAndAdditions) {
  build_arsc_file_and_validate([&](const std::string& /* unused */,
                                   const std::string& arsc_path) {
    auto initial_dump = aapt_dump_and_parse(arsc_path);
    uint32_t resource_id = 0x7f020000;
    auto initial_values =
        initial_dump.get_complex_values("xxhdpi", resource_id);
    ASSERT_EQ(initial_values.size(), 2);
    EXPECT_EQ(initial_values[0].key, 0x01010098);
    EXPECT_EQ(initial_values[1].key, 0x010100d4);

    {
      ResourcesArscFile arsc_file(arsc_path);
      std::vector<resources::StyleModificationSpec::Modification> modifications;

      resources::StyleModificationSpec::Modification removal{resource_id,
                                                             0x010100d4};
      modifications.push_back(removal);

      resources::StyleModificationSpec::Modification addition1{
          resource_id, 0x01010099,
          resources::StyleResource::Value{
              android::Res_value::TYPE_INT_COLOR_RGB8, 0xFFFFFFFF}};
      resources::StyleModificationSpec::Modification addition2{
          resource_id, 0x01010100,
          resources::StyleResource::Value{
              android::Res_value::TYPE_INT_COLOR_RGB8, 0xFF000000}};
      modifications.push_back(addition1);
      modifications.push_back(addition2);

      arsc_file.apply_attribute_removals_and_additions(modifications, {});
    }

    {
      auto modified_dump = aapt_dump_and_parse(arsc_path);
      auto modified_values =
          modified_dump.get_complex_values("xxhdpi", resource_id);
      ASSERT_EQ(modified_values.size(), 3);

      EXPECT_EQ(modified_values[0].key, 0x01010098);

      EXPECT_EQ(modified_values[1].key, 0x01010099);
      EXPECT_EQ(modified_values[1].data, 0xFFFFFFFF);
      EXPECT_EQ(modified_values[2].key, 0x01010100);
      EXPECT_EQ(modified_values[2].data, 0xFF000000);

      for (const auto& value : modified_values) {
        EXPECT_NE(value.key, 0x010100d4) << "Removed attribute still present";
      }
    }
  });
}

TEST(ResourcesArscFile, ApplyStyleMerges) {
  uint32_t resource_id = 0x7f020000;
  uint32_t nonexistent_resource_id = 0x7f030000;
  uint32_t new_parent_id = 0x01010200;
  uint32_t alternate_parent_id = 0x01010300;

  auto verify_style_attributes = [&new_parent_id](
                                     const std::vector<ComplexValue>& values,
                                     uint32_t expected_parent_id) {
    UnorderedMap<uint32_t, uint32_t> attr_map;
    for (const auto& value : values) {
      attr_map[value.key] = value.data;
    }

    EXPECT_TRUE(attr_map.find(kTextColorAttrId) != attr_map.end())
        << "Original attribute kTextColorAttrId not found";
    EXPECT_TRUE(attr_map.find(kBackgroundAttrId) != attr_map.end())
        << "Original attribute kBackgroundAttrId not found";

    if (expected_parent_id == new_parent_id) {
      EXPECT_TRUE(attr_map.find(kFloatAttrId) != attr_map.end())
          << "New attribute kFloatAttrId not found";
      EXPECT_TRUE(attr_map.find(kTextColorHintAttrId) != attr_map.end())
          << "New attribute kTextColorHintAttrId not found";

      EXPECT_EQ(attr_map[kFloatAttrId], kColorPurple);
      EXPECT_EQ(attr_map[kTextColorHintAttrId], kColorTeal);
    }

    return attr_map.size();
  };

  build_arsc_file_and_validate([&](const std::string& /* unused */,
                                   const std::string& arsc_path) {
    auto initial_dump = aapt_dump_and_parse(arsc_path);
    auto initial_values =
        initial_dump.get_complex_values("xxhdpi", resource_id);
    ASSERT_EQ(initial_values.size(), 2);
    EXPECT_EQ(initial_values[0].key, kTextColorAttrId);
    EXPECT_EQ(initial_values[1].key, kBackgroundAttrId);

    auto initial_parent_id =
        initial_dump.config_to_complex_values["xxhdpi"][resource_id].parent_id;
    EXPECT_EQ(initial_parent_id, 0);

    {
      ResourcesArscFile arsc_file(arsc_path);

      UnorderedMap<uint32_t, resources::StyleResource::Value> attributes;
      attributes.insert(
          {kFloatAttrId,
           resources::StyleResource::Value(
               android::Res_value::TYPE_INT_COLOR_RGB8, kColorPurple)});
      attributes.insert(
          {kTextColorHintAttrId,
           resources::StyleResource::Value(
               android::Res_value::TYPE_INT_COLOR_RGB8, kColorTeal)});

      std::vector<resources::StyleModificationSpec::Modification> modifications;
      modifications.emplace_back(resource_id, new_parent_id,
                                 std::move(attributes));

      arsc_file.apply_style_merges(modifications, {});
    }

    {
      auto modified_dump = aapt_dump_and_parse(arsc_path);

      auto modified_parent_id =
          modified_dump.config_to_complex_values["xxhdpi"][resource_id]
              .parent_id;
      EXPECT_EQ(modified_parent_id, new_parent_id);

      auto modified_values =
          modified_dump.get_complex_values("xxhdpi", resource_id);
      ASSERT_GE(modified_values.size(), 4);
      verify_style_attributes(modified_values, new_parent_id);
    }

    {
      ResourcesArscFile arsc_file(arsc_path);

      UnorderedMap<uint32_t, resources::StyleResource::Value> attributes;
      attributes.insert(
          {kPaddingAttrId,
           resources::StyleResource::Value(
               android::Res_value::TYPE_INT_COLOR_RGB8, kColorBlue)});

      std::vector<resources::StyleModificationSpec::Modification> modifications;
      modifications.emplace_back(nonexistent_resource_id, alternate_parent_id,
                                 std::move(attributes));

      arsc_file.apply_style_merges(modifications, {});
    }

    {
      auto final_dump = aapt_dump_and_parse(arsc_path);

      auto final_parent_id =
          final_dump.config_to_complex_values["xxhdpi"][resource_id].parent_id;
      EXPECT_EQ(final_parent_id, new_parent_id);

      auto final_values = final_dump.get_complex_values("xxhdpi", resource_id);
      ASSERT_GE(final_values.size(), 4);
      verify_style_attributes(final_values, new_parent_id);
    }
  });
}

TEST(ResourcesArscFile, AddStyles) {
  build_arsc_file_and_validate([&](const std::string& /* unused */,
                                   const std::string& arsc_path) {
    auto initial_dump = aapt_dump_and_parse(arsc_path);

    EXPECT_EQ(initial_dump.get_identifier("foo:style/fourth"), 0x7f020000);

    auto initial_values = initial_dump.get_complex_values("xxhdpi", 0x7f020000);
    ASSERT_EQ(initial_values.size(), 2);
    EXPECT_EQ(initial_values[0].key, 0x01010098);
    EXPECT_EQ(initial_values[1].key, 0x010100d4);

    {
      ResourcesArscFile arsc_file(arsc_path);

      std::vector<resources::StyleModificationSpec::Modification> modifications;

      resources::StyleModificationSpec::Modification new_style1(0x7f020001);
      modifications.push_back(new_style1);

      resources::StyleModificationSpec::Modification new_style2(0x7f020002);
      modifications.push_back(new_style2);

      arsc_file.add_styles(modifications, {});
    }

    {
      auto modified_dump = aapt_dump_and_parse(arsc_path);

      EXPECT_EQ(modified_dump.get_identifier("foo:style/fourth"), 0x7f020000);
      auto original_values =
          modified_dump.get_complex_values("default", 0x7f020000);
      ASSERT_EQ(original_values.size(), 2);
      EXPECT_EQ(original_values[0].key, 0x01010098);
      EXPECT_EQ(original_values[1].key, 0x010100d4);

      auto style_1_values =
          modified_dump.get_complex_values("default", 0x7f020001);
      EXPECT_EQ(style_1_values.size(), 0)
          << "New style 0x7f020001 should be empty";

      auto style_2_values =
          modified_dump.get_complex_values("default", 0x7f020002);
      EXPECT_EQ(style_2_values.size(), 0)
          << "New style 0x7f020002 should be empty";

      EXPECT_EQ(modified_dump.config_to_complex_values["default"][0x7f020001]
                    .parent_id,
                0);
      EXPECT_EQ(modified_dump.config_to_complex_values["default"][0x7f020002]
                    .parent_id,
                0);
    }
  });
}
